Update #37 - Touch support without userscript, many other feats

This commit is contained in:
lax1dude
2024-09-21 20:17:42 -07:00
parent 173727c8c4
commit ec1ab8ece3
683 changed files with 62074 additions and 8996 deletions

View File

@ -0,0 +1,84 @@
package net.lax1dude.eaglercraft.v1_8.cookie;
import java.text.SimpleDateFormat;
import java.util.Date;
import net.lax1dude.eaglercraft.v1_8.cookie.ServerCookieDataStore.ServerCookie;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class GuiScreenInspectSessionToken extends GuiScreen {
private final GuiScreen parent;
private final ServerCookie cookie;
public GuiScreenInspectSessionToken(GuiScreenRevokeSessionToken parent, ServerCookie cookie) {
this.parent = parent;
this.cookie = cookie;
}
public void initGui() {
this.buttonList.clear();
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 106, I18n.format("gui.done")));
}
public void drawScreen(int par1, int par2, float par3) {
this.drawDefaultBackground();
String[][] toDraw = new String[][] {
{
I18n.format("inspectSessionToken.details.server"),
I18n.format("inspectSessionToken.details.expires"),
I18n.format("inspectSessionToken.details.length")
},
{
cookie.server.length() > 32 ? cookie.server.substring(0, 30) + "..." : cookie.server,
(new SimpleDateFormat("M/d/yyyy h:mm aa")).format(new Date(cookie.expires)),
Integer.toString(cookie.cookie.length)
}
};
int[] maxWidth = new int[2];
for(int i = 0; i < 2; ++i) {
String[] strs = toDraw[i];
int w = 0;
for(int j = 0; j < strs.length; ++j) {
int k = fontRendererObj.getStringWidth(strs[j]);
if(k > w) {
w = k;
}
}
maxWidth[i] = w + 10;
}
int totalWidth = maxWidth[0] + maxWidth[1];
this.drawCenteredString(fontRendererObj, I18n.format("inspectSessionToken.title"), this.width / 2, 70, 16777215);
this.drawString(fontRendererObj, toDraw[0][0], (this.width - totalWidth) / 2, 90, 11184810);
this.drawString(fontRendererObj, toDraw[0][1], (this.width - totalWidth) / 2, 104, 11184810);
this.drawString(fontRendererObj, toDraw[0][2], (this.width - totalWidth) / 2, 118, 11184810);
this.drawString(fontRendererObj, toDraw[1][0], (this.width - totalWidth) / 2 + maxWidth[0], 90, 11184810);
this.drawString(fontRendererObj, toDraw[1][1], (this.width - totalWidth) / 2 + maxWidth[0], 104, 11184810);
this.drawString(fontRendererObj, toDraw[1][2], (this.width - totalWidth) / 2 + maxWidth[0], 118, 11184810);
super.drawScreen(par1, par2, par3);
}
protected void actionPerformed(GuiButton par1GuiButton) {
if(par1GuiButton.id == 0) {
this.mc.displayGuiScreen(parent);
}
}
}

View File

@ -0,0 +1,146 @@
package net.lax1dude.eaglercraft.v1_8.cookie;
import java.io.IOException;
import java.util.Collections;
import com.google.common.collect.Lists;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSlot;
import net.minecraft.client.resources.I18n;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class GuiScreenRevokeSessionToken extends GuiScreen {
protected GuiScreen parentScreen;
private GuiScreenRevokeSessionToken.List list;
private GuiButton inspectButton;
private GuiButton revokeButton;
public GuiScreenRevokeSessionToken(GuiScreen parent) {
this.parentScreen = parent;
}
public void initGui() {
this.buttonList.clear();
this.buttonList.add(this.inspectButton = new GuiButton(10, this.width / 2 - 154, this.height - 38, 100, 20, I18n.format("revokeSessionToken.inspect")));
this.buttonList.add(this.revokeButton = new GuiButton(9, this.width / 2 - 50, this.height - 38, 100, 20, I18n.format("revokeSessionToken.revoke")));
this.buttonList.add(new GuiButton(6, this.width / 2 + 54, this.height - 38, 100, 20, I18n.format("gui.done")));
this.list = new GuiScreenRevokeSessionToken.List(this.mc);
this.list.registerScrollButtons(7, 8);
updateButtons();
}
public void handleMouseInput() throws IOException {
super.handleMouseInput();
this.list.handleMouseInput();
}
public void handleTouchInput() throws IOException {
super.handleTouchInput();
this.list.handleTouchInput();
}
protected void actionPerformed(GuiButton parGuiButton) {
if (parGuiButton.enabled) {
switch (parGuiButton.id) {
case 6:
this.mc.displayGuiScreen(this.parentScreen);
break;
case 9:
String s1 = list.getSelectedItem();
if(s1 != null) {
ServerCookieDataStore.ServerCookie cookie = ServerCookieDataStore.loadCookie(s1);
if(cookie != null) {
this.mc.displayGuiScreen(new GuiScreenSendRevokeRequest(this, cookie));
}else {
this.initGui();
}
}
break;
case 10:
String s2 = list.getSelectedItem();
if(s2 != null) {
ServerCookieDataStore.ServerCookie cookie = ServerCookieDataStore.loadCookie(s2);
if(cookie != null) {
this.mc.displayGuiScreen(new GuiScreenInspectSessionToken(this, cookie));
}else {
this.initGui();
}
}
break;
default:
this.list.actionPerformed(parGuiButton);
}
}
}
protected void updateButtons() {
inspectButton.enabled = revokeButton.enabled = list.getSelectedItem() != null;
}
public void drawScreen(int i, int j, float f) {
this.list.drawScreen(i, j, f);
this.drawCenteredString(this.fontRendererObj, I18n.format("revokeSessionToken.title"), this.width / 2, 16, 16777215);
this.drawCenteredString(this.fontRendererObj, I18n.format("revokeSessionToken.note.0"), this.width / 2, this.height - 66, 8421504);
this.drawCenteredString(this.fontRendererObj, I18n.format("revokeSessionToken.note.1"), this.width / 2, this.height - 56, 8421504);
super.drawScreen(i, j, f);
}
class List extends GuiSlot {
private final java.util.List<String> cookieNames = Lists.newArrayList();
public List(Minecraft mcIn) {
super(mcIn, GuiScreenRevokeSessionToken.this.width, GuiScreenRevokeSessionToken.this.height, 32, GuiScreenRevokeSessionToken.this.height - 75 + 4, 18);
ServerCookieDataStore.flush();
cookieNames.addAll(ServerCookieDataStore.getRevokableServers());
Collections.sort(cookieNames);
}
protected int getSize() {
return this.cookieNames.size();
}
protected void elementClicked(int i, boolean var2, int var3, int var4) {
selectedElement = i;
GuiScreenRevokeSessionToken.this.updateButtons();
}
protected boolean isSelected(int i) {
return selectedElement == i;
}
protected String getSelectedItem() {
return selectedElement == -1 ? null : cookieNames.get(selectedElement);
}
protected int getContentHeight() {
return this.getSize() * 18;
}
protected void drawBackground() {
GuiScreenRevokeSessionToken.this.drawDefaultBackground();
}
protected void drawSlot(int i, int var2, int j, int var4, int var5, int var6) {
GuiScreenRevokeSessionToken.this.drawCenteredString(GuiScreenRevokeSessionToken.this.fontRendererObj,
this.cookieNames.get(i), this.width / 2, j + 1, 16777215);
}
}
}

View File

@ -0,0 +1,177 @@
package net.lax1dude.eaglercraft.v1_8.cookie;
import org.json.JSONObject;
import net.lax1dude.eaglercraft.v1_8.cookie.ServerCookieDataStore.ServerCookie;
import net.lax1dude.eaglercraft.v1_8.internal.IServerQuery;
import net.lax1dude.eaglercraft.v1_8.internal.QueryResponse;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.minecraft.GuiScreenGenericErrorMessage;
import net.lax1dude.eaglercraft.v1_8.socket.ServerQueryDispatch;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class GuiScreenSendRevokeRequest extends GuiScreen {
private static final Logger logger = LogManager.getLogger("SessionRevokeRequest");
private GuiScreen parent;
private ServerCookie cookie;
private String title;
private String message;
private int timer = 0;
private boolean cancelRequested = false;
private IServerQuery query = null;
private boolean hasSentPacket = false;
public GuiScreenSendRevokeRequest(GuiScreen parent, ServerCookie cookie) {
this.parent = parent;
this.cookie = cookie;
this.title = I18n.format("revokeSendingScreen.title");
this.message = I18n.format("revokeSendingScreen.message.opening", cookie.server);
}
public void initGui() {
this.buttonList.clear();
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 96, I18n.format("gui.cancel")));
}
public void drawScreen(int par1, int par2, float par3) {
this.drawDefaultBackground();
this.drawCenteredString(fontRendererObj, title, this.width / 2, 70, 11184810);
this.drawCenteredString(fontRendererObj, message, this.width / 2, 90, 16777215);
super.drawScreen(par1, par2, par3);
}
public void updateScreen() {
++timer;
if (timer > 1) {
if(query == null) {
logger.info("Attempting to revoke session tokens for: {}", cookie.server);
query = ServerQueryDispatch.sendServerQuery(cookie.server, "revoke_session_token");
if(query == null) {
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.connectionError", parent));
return;
}
}else {
query.update();
QueryResponse resp = query.getResponse();
if(resp != null) {
if(resp.responseType.equalsIgnoreCase("revoke_session_token") && (hasSentPacket ? resp.isResponseJSON() : resp.isResponseString())) {
if(!hasSentPacket) {
String str = resp.getResponseString();
if("ready".equalsIgnoreCase(str)) {
hasSentPacket = true;
message = I18n.format("revokeSendingScreen.message.sending");
query.send(cookie.cookie);
return;
}else {
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.clientError", parent));
return;
}
}else {
JSONObject json = resp.getResponseJSON();
String stat = json.optString("status");
if("ok".equalsIgnoreCase(stat)) {
if(hasSentPacket) {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeSuccess.title", "revokeSuccess.desc", parent));
ServerCookieDataStore.clearCookie(cookie.server);
return;
}else {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.clientError", parent));
return;
}
}else if("error".equalsIgnoreCase(stat)) {
int code = json.optInt("code", -1);
if(code == -1) {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.clientError", parent));
return;
}else {
String key;
switch(code) {
case 1:
key = "revokeFailure.desc.notSupported";
break;
case 2:
key = "revokeFailure.desc.notAllowed";
break;
case 3:
key = "revokeFailure.desc.notFound";
break;
case 4:
key = "revokeFailure.desc.serverError";
break;
default:
key = "revokeFailure.desc.genericCode";
break;
}
logger.error("Recieved error code {}! ({})", code, key);
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", key, parent));
if(json.optBoolean("delete", false)) {
ServerCookieDataStore.clearCookie(cookie.server);
}
return;
}
}else {
logger.error("Recieved unknown status \"{}\"!", stat);
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.clientError", parent));
return;
}
}
}else {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.clientError", parent));
return;
}
}
if(query.isClosed()) {
if(!hasSentPacket || query.responsesAvailable() == 0) {
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.connectionError", parent));
return;
}
}else {
if(timer > 400) {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.connectionError", parent));
return;
}
}
if(cancelRequested) {
query.close();
this.mc.displayGuiScreen(new GuiScreenGenericErrorMessage("revokeFailure.title", "revokeFailure.desc.cancelled", parent));
return;
}
}
}
}
protected void actionPerformed(GuiButton par1GuiButton) {
if(par1GuiButton.id == 0) {
cancelRequested = true;
par1GuiButton.enabled = false;
}
}
}

View File

@ -0,0 +1,294 @@
package net.lax1dude.eaglercraft.v1_8.cookie;
import net.lax1dude.eaglercraft.v1_8.internal.IFramebufferGL;
import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL;
import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformAssets;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer;
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.ImageData;
import net.lax1dude.eaglercraft.v1_8.opengl.VSHInputLayoutParser;
import net.minecraft.client.renderer.texture.TextureUtil;
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.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists;
import net.lax1dude.eaglercraft.v1_8.Base64;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom;
import net.lax1dude.eaglercraft.v1_8.crypto.GeneralDigest;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA256Digest;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class HardwareFingerprint {
// This is used for generating encryption keys for storing cookies,
// its supposed to make session hijacking more difficult for skids
private static final String fingerprintIMG = "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";
private static final String shaderPrecision = "precision lowp int;\nprecision mediump float;\nprecision mediump sampler2D;\n";
private static byte[] fingerprint = null;
public static final Logger logger = LogManager.getLogger("HardwareFingerprint");
public static byte[] getFingerprint() {
if(fingerprint == null) {
try {
fingerprint = generateFingerprint();
}catch(Throwable t) {
fingerprint = new byte[0];
}
if(fingerprint.length == 0) {
logger.error("Failed to calculate hardware fingerprint, server cookies will not be encrypted!");
}
}
return fingerprint;
}
private static byte[] generateFingerprint() {
ImageData img = PlatformAssets.loadImageFile(Base64.decodeBase64(fingerprintIMG), "image/jpeg");
if(img == null) {
logger.error("Input image data is corrupt!");
return new byte[0];
}
int[][] mipmapLevels = TextureUtil.generateMipmapData(7, 128,
new int[][] { img.pixels, null, null, null, null, null, null, null });
int helperTexture = GlStateManager.generateTexture();
GlStateManager.bindTexture(helperTexture);
_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_MAG_FILTER, GL_NEAREST);
TextureUtil.allocateTextureImpl(helperTexture, 7, 128, 128);
TextureUtil.uploadTextureMipmap(mipmapLevels, 128, 128, 0, 0, false, false);
if(checkAnisotropicFilteringSupport()) {
_wglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 16.0f);
}
IShaderGL vert;
List<VSHInputLayoutParser.ShaderInput> vertLayout;
if(DrawUtils.vshLocal != null) {
vert = DrawUtils.vshLocal;
vertLayout = DrawUtils.vshLocalLayout;
}else {
String vshLocalSrc = EagRuntime.getRequiredResourceString("/assets/eagler/glsl/local.vsh");
vertLayout = VSHInputLayoutParser.getShaderInputs(vshLocalSrc);
vert = _wglCreateShader(GL_VERTEX_SHADER);
_wglShaderSource(vert, GLSLHeader.getVertexHeaderCompat(vshLocalSrc, DrawUtils.vertexShaderPrecision));
_wglCompileShader(vert);
if(_wglGetShaderi(vert, GL_COMPILE_STATUS) != GL_TRUE) {
_wglDeleteShader(vert);
GlStateManager.deleteTexture(helperTexture);
return new byte[0];
}
}
IShaderGL frag = _wglCreateShader(GL_FRAGMENT_SHADER);
_wglShaderSource(frag, GLSLHeader.getFragmentHeaderCompat(EagRuntime.getRequiredResourceString("/assets/eagler/glsl/hw_fingerprint.fsh"), shaderPrecision));
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
_wglDeleteShader(vert);
_wglDeleteShader(frag);
GlStateManager.deleteTexture(helperTexture);
return new byte[0];
}
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);
if(DrawUtils.vshLocal == null) {
_wglDeleteShader(vert);
}
_wglDeleteShader(frag);
if(_wglGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
_wglDeleteProgram(program);
GlStateManager.deleteTexture(helperTexture);
return new byte[0];
}
EaglercraftGPU.bindGLShaderProgram(program);
_wglUniform1i(_wglGetUniformLocation(program, "u_inputTexture"), 0);
float fovy = 90.0f;
float aspect = 1.0f;
float zNear = 0.01f;
float zFar = 100.0f;
FloatBuffer matrixUploadBuffer = EagRuntime.allocateFloatBuffer(16);
float toRad = 0.0174532925f;
float cotangent = (float) Math.cos(fovy * toRad * 0.5f) / (float) Math.sin(fovy * toRad * 0.5f);
matrixUploadBuffer.put(cotangent / aspect);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(cotangent);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put((zFar + zNear) / (zFar - zNear));
matrixUploadBuffer.put(2.0f * zFar * zNear / (zFar - zNear));
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.put(-1.0f);
matrixUploadBuffer.put(0.0f);
matrixUploadBuffer.flip();
_wglUniformMatrix4fv(_wglGetUniformLocation(program, "u_textureMatrix"), false, matrixUploadBuffer);
EagRuntime.freeFloatBuffer(matrixUploadBuffer);
int[] oldViewport = new int[4];
EaglercraftGPU.glGetInteger(GL_VIEWPORT, oldViewport);
IFramebufferGL framebuffer = _wglCreateFramebuffer();
int renderTexture = GlStateManager.generateTexture();
GlStateManager.bindTexture(renderTexture);
_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_WRAP_S, GL_REPEAT);
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
int dataLength;
int type;
if(EaglercraftGPU.checkHDRFramebufferSupport(32)) {
dataLength = 256 * 256 * 4 * 4;
type = GL_FLOAT;
EaglercraftGPU.createFramebufferHDR32FTexture(GL_TEXTURE_2D, 0, 256, 256, GL_RGBA, false);
}else if(EaglercraftGPU.checkHDRFramebufferSupport(16)) {
dataLength = 256 * 256 * 4 * 2;
type = _GL_HALF_FLOAT;
EaglercraftGPU.createFramebufferHDR16FTexture(GL_TEXTURE_2D, 0, 256, 256, GL_RGBA, false);
}else {
dataLength = 256 * 256 * 4;
type = GL_UNSIGNED_BYTE;
EaglercraftGPU.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null);
}
_wglBindFramebuffer(_GL_FRAMEBUFFER, framebuffer);
_wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, EaglercraftGPU.getNativeTexture(renderTexture), 0);
_wglDrawBuffers(_GL_COLOR_ATTACHMENT0);
GlStateManager.viewport(0, 0, 256, 256);
GlStateManager.disableBlend();
GlStateManager.bindTexture(helperTexture);
DrawUtils.drawStandardQuad2D();
_wglDeleteProgram(program);
GlStateManager.deleteTexture(helperTexture);
ByteBuffer readBuffer = EagRuntime.allocateByteBuffer(dataLength);
EaglercraftGPU.glReadPixels(0, 0, 256, 256, GL_RGBA, type, readBuffer);
_wglBindFramebuffer(_GL_FRAMEBUFFER, null);
_wglDeleteFramebuffer(framebuffer);
GlStateManager.deleteTexture(renderTexture);
GlStateManager.viewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
SHA256Digest digest = new SHA256Digest();
byte[] copyBuffer = new byte[1024];
byte[] b = ("eag" + EaglercraftGPU.glGetString(GL_VENDOR) + "; eag " + EaglercraftGPU.glGetString(GL_RENDERER)).getBytes(StandardCharsets.UTF_8);
digest.update(b, 0, b.length);
digestInts(digest, _wglGetInteger(0x8869), _wglGetInteger(0x8DFB), _wglGetInteger(0x8B4C), _wglGetInteger(0x8DFC), copyBuffer);
digestInts(digest, _wglGetInteger(0x8DFD), _wglGetInteger(0x8872), _wglGetInteger(0x84E8), 69, copyBuffer);
digestInts(digest, _wglGetInteger(0x0D33), _wglGetInteger(0x851C), _wglGetInteger(0x8B4D), 69, copyBuffer);
if(EaglercraftGPU.checkOpenGLESVersion() >= 300) {
digestInts(digest, _wglGetInteger(0x8B4A), _wglGetInteger(0x8A2B), _wglGetInteger(0x9122), _wglGetInteger(0x8B4B), copyBuffer);
digestInts(digest, _wglGetInteger(0x8C8A), _wglGetInteger(0x8C8B), _wglGetInteger(0x8C80), _wglGetInteger(0x8B49), copyBuffer);
digestInts(digest, _wglGetInteger(0x8A2D), _wglGetInteger(0x9125), _wglGetInteger(0x8904), _wglGetInteger(0x8905), copyBuffer);
digestInts(digest, _wglGetInteger(0x8824), _wglGetInteger(0x8073), _wglGetInteger(0x88FF), _wglGetInteger(0x84FD), copyBuffer);
digestInts(digest, _wglGetInteger(0x8CDF), _wglGetInteger(0x8A2F), _wglGetInteger(0x8A30), _wglGetInteger(0x8A34), copyBuffer);
digestInts(digest, _wglGetInteger(0x8A2E), _wglGetInteger(0x8A31), _wglGetInteger(0x8A33), _wglGetInteger(0x8D57), copyBuffer);
}
try {
List<String> exts = Lists.newArrayList(getAllExtensions());
Collections.sort(exts);
EaglercraftRandom rand = new EaglercraftRandom(6942069420l + exts.size() * 69l + b.length);
for (int i = exts.size() - 1; i > 0; --i) {
int j = rand.nextInt(i + 1);
Collections.swap(exts, i, j);
}
b = String.join(":>", exts).getBytes(StandardCharsets.UTF_8);
digest.update(b, 0, b.length);
}catch(Throwable t) {
}
int i;
while(readBuffer.hasRemaining()) {
i = Math.min(readBuffer.remaining(), copyBuffer.length);
readBuffer.get(copyBuffer, 0, i);
digest.update(copyBuffer, 0, i);
}
EagRuntime.freeByteBuffer(readBuffer);
byte[] hashOut = new byte[32];
digest.doFinal(hashOut, 0);
return hashOut;
}
private static void digestInts(GeneralDigest digest, int i1, int i2, int i3, int i4, byte[] tmpBuffer) {
tmpBuffer[0] = (byte)(i1 >>> 24);
tmpBuffer[1] = (byte)(i1 >>> 16);
tmpBuffer[2] = (byte)(i1 >>> 8);
tmpBuffer[3] = (byte)(i1 & 0xFF);
tmpBuffer[4] = (byte)(i2 >>> 24);
tmpBuffer[5] = (byte)(i2 >>> 16);
tmpBuffer[6] = (byte)(i2 >>> 8);
tmpBuffer[7] = (byte)(i2 & 0xFF);
tmpBuffer[8] = (byte)(i3 >>> 24);
tmpBuffer[9] = (byte)(i3 >>> 16);
tmpBuffer[10] = (byte)(i3 >>> 8);
tmpBuffer[11] = (byte)(i3 & 0xFF);
tmpBuffer[12] = (byte)(i4 >>> 24);
tmpBuffer[13] = (byte)(i4 >>> 16);
tmpBuffer[14] = (byte)(i4 >>> 8);
tmpBuffer[15] = (byte)(i4 & 0xFF);
digest.update(tmpBuffer, 0, 16);
}
}

View File

@ -0,0 +1,396 @@
package net.lax1dude.eaglercraft.v1_8.cookie;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.EaglerOutputStream;
import net.lax1dude.eaglercraft.v1_8.EaglerZLIB;
import net.lax1dude.eaglercraft.v1_8.crypto.AESLightEngine;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ServerCookieDataStore {
private static final Logger logger = LogManager.getLogger("ServerCookieDataStore");
private static final Map<String,ServerCookie> dataStore = new HashMap<>();
public static final String localStorageKey = "c";
public static class ServerCookie {
public final String server;
public final byte[] cookie;
public final long expires;
public final boolean revokeQuerySupported;
public final boolean saveCookieToDisk;
public ServerCookie(String server, byte[] cookie, long expires, boolean revokeQuerySupported, boolean saveCookieToDisk) {
this.server = server;
this.cookie = cookie;
this.expires = expires;
this.revokeQuerySupported = revokeQuerySupported;
this.saveCookieToDisk = saveCookieToDisk;
}
}
public static void load() {
if(EagRuntime.getConfiguration().isEnableServerCookies()) {
loadData(HardwareFingerprint.getFingerprint());
}
}
public static ServerCookie loadCookie(String server) {
if(!EagRuntime.getConfiguration().isEnableServerCookies()) {
return null;
}
server = normalize(server);
ServerCookie cookie = dataStore.get(server);
if(cookie == null) {
return null;
}
long timestamp = System.currentTimeMillis();
if(timestamp > cookie.expires) {
dataStore.remove(server);
saveData(HardwareFingerprint.getFingerprint());
return null;
}
return cookie;
}
public static void saveCookie(String server, long expires, byte[] data, boolean revokeQuerySupported, boolean saveCookieToDisk) {
if(!EagRuntime.getConfiguration().isEnableServerCookies()) {
return;
}
server = normalize(server);
if(expires > 604800l) {
clearCookie(server);
logger.error("Server \"{}\" tried to set a cookie for {} days! (The max is 7 days)", server, (expires / 604800l));
return;
}
if(data.length > 255) {
clearCookie(server);
logger.error("Server \"{}\" tried to set a {} byte cookie! (The max length is 255 bytes)", server, data.length);
return;
}
if(expires < 0l || data.length == 0) {
clearCookie(server);
return;
}
long expiresRelative = System.currentTimeMillis() + expires * 1000l;
dataStore.put(server, new ServerCookie(server, data, expiresRelative, revokeQuerySupported, saveCookieToDisk));
saveData(HardwareFingerprint.getFingerprint());
}
public static void clearCookie(String server) {
if(!EagRuntime.getConfiguration().isEnableServerCookies()) {
return;
}
if(dataStore.remove(normalize(server)) != null) {
saveData(HardwareFingerprint.getFingerprint());
}
}
public static void clearCookiesLow() {
dataStore.clear();
}
private static String normalize(String server) {
int j = server.indexOf('/');
if(j != -1) {
int i = server.indexOf("://");
if(i != -1) {
j = server.indexOf('/', i + 3);
if(j == -1) {
return server.toLowerCase();
}else {
return server.substring(0, j).toLowerCase() + server.substring(j);
}
}else {
return server.substring(0, j).toLowerCase() + server.substring(j);
}
}else {
return server.toLowerCase();
}
}
public static Set<String> getAllServers() {
return dataStore.keySet();
}
public static List<String> getRevokableServers() {
List<String> ret = new ArrayList<>(dataStore.size());
for(ServerCookie c : dataStore.values()) {
if(c.revokeQuerySupported) {
ret.add(c.server);
}
}
return ret;
}
public static int size() {
return dataStore.size();
}
public static int numRevokable() {
int i = 0;
for(ServerCookie c : dataStore.values()) {
if(c.revokeQuerySupported) {
++i;
}
}
return i;
}
public static void flush() {
Iterator<ServerCookie> itr = dataStore.values().iterator();
boolean changed = false;
while(itr.hasNext()) {
long timestamp = System.currentTimeMillis();
ServerCookie cookie = itr.next();
if(timestamp > cookie.expires) {
itr.remove();
changed = true;
}
}
if(changed) {
saveData(HardwareFingerprint.getFingerprint());
}
}
private static void loadData(byte[] key) {
dataStore.clear();
byte[] cookiesTag = PlatformApplication.getLocalStorage(localStorageKey, false);
if(cookiesTag == null) {
return;
}
if(cookiesTag.length <= 25) {
PlatformApplication.setLocalStorage(localStorageKey, null, false);
return;
}
try {
byte[] decrypted;
int decryptedLen;
switch(cookiesTag[0]) {
case 2:
if(key == null || key.length == 0) {
throw new IOException("Data is encrypted!");
}
decrypted = new byte[cookiesTag.length - 5];
decryptedLen = (cookiesTag[1] << 24) | (cookiesTag[2] << 16) | (cookiesTag[3] << 8) | (cookiesTag[4] & 0xFF);
if(decryptedLen < 25) {
throw new IOException("too short!");
}
AESLightEngine aes = new AESLightEngine();
aes.init(false, key);
int bs = aes.getBlockSize();
if(decrypted.length % bs != 0) {
throw new IOException("length not aligned to block size!");
}
byte[] cbcHelper = new byte[] { (byte) 29, (byte) 163, (byte) 4, (byte) 20, (byte) 207, (byte) 26,
(byte) 140, (byte) 55, (byte) 246, (byte) 250, (byte) 141, (byte) 183, (byte) 153, (byte) 154,
(byte) 59, (byte) 4 };
for(int i = 0; i < decryptedLen; i += bs) {
processBlockDecryptHelper(aes, cookiesTag, 5 + i, decrypted, i, bs, Math.min(decryptedLen - i, bs), cbcHelper);
}
if(decrypted[0] != (byte)0x69) {
throw new IOException("Data is corrupt!");
}
break;
case 1:
if(key != null && key.length > 0) {
throw new IOException("Data isn't encrypted!");
}
decrypted = cookiesTag;
decryptedLen = cookiesTag.length;
break;
default:
throw new IOException("Unknown type!");
}
SHA1Digest digest = new SHA1Digest();
digest.update(decrypted, 25, decryptedLen - 25);
byte[] digestOut = new byte[20];
digest.doFinal(digestOut, 0);
for(int i = 0; i < 20; ++i) {
if(digestOut[i] != decrypted[5 + i]) {
throw new IOException("Invalid checksum!");
}
}
int decompressedLen = (decrypted[1] << 24) | (decrypted[2] << 16) | (decrypted[3] << 8) | (decrypted[4] & 0xFF);
byte[] decompressed = new byte[decompressedLen];
try (InputStream zstream = EaglerZLIB.newInflaterInputStream(new EaglerInputStream(decrypted, 25, decryptedLen - 25))) {
int i = 0, j;
while(i < decompressedLen && (j = zstream.read(decompressed, i, decompressedLen - i)) != -1) {
i += j;
}
if(i != decompressedLen) {
throw new IOException("Length does not match!");
}
}
DataInputStream dis = new DataInputStream(new EaglerInputStream(decompressed));
int readCount = dis.readInt();
long time = System.currentTimeMillis();
for(int i = 0; i < readCount; ++i) {
byte flags = dis.readByte();
long expires = dis.readLong();
int len = dis.readUnsignedShort();
String server = dis.readUTF();
if(len == 0) {
continue;
}
if(expires < time) {
dis.skipBytes(len);
continue;
}
byte[] cookieData = new byte[len];
dis.readFully(cookieData);
server = normalize(server);
dataStore.put(server, new ServerCookie(server, cookieData, expires, (flags & 1) != 0, (flags & 2) != 0));
}
if(dis.available() > 0) {
throw new IOException("Extra bytes remaining!");
}
}catch (IOException e) {
dataStore.clear();
PlatformApplication.setLocalStorage(localStorageKey, null, false);
return;
}
}
private static void saveData(byte[] key) {
Iterator<ServerCookie> itr = dataStore.values().iterator();
List<ServerCookie> toSave = new ArrayList<>(dataStore.size());
while(itr.hasNext()) {
long timestamp = System.currentTimeMillis();
ServerCookie cookie = itr.next();
if(timestamp > cookie.expires || cookie.cookie.length > 255 || cookie.cookie.length == 0) {
itr.remove();
}else if(cookie.saveCookieToDisk) {
toSave.add(cookie);
}
}
if(toSave.size() == 0) {
PlatformApplication.setLocalStorage(localStorageKey, null, false);
}else {
EaglerOutputStream bao = new EaglerOutputStream(1024);
bao.skipBytes(25);
int totalUncompressedLen;
try(DataOutputStream zstream = new DataOutputStream(EaglerZLIB.newDeflaterOutputStream(bao))) {
zstream.writeInt(dataStore.size());
for(Entry<String,ServerCookie> etr : dataStore.entrySet()) {
ServerCookie cookie = etr.getValue();
zstream.writeByte((cookie.revokeQuerySupported ? 1 : 0) | (cookie.saveCookieToDisk ? 2 : 0));
zstream.writeLong(cookie.expires);
zstream.writeShort(cookie.cookie.length);
zstream.writeUTF(etr.getKey());
zstream.write(cookie.cookie);
}
totalUncompressedLen = zstream.size();
} catch (IOException e) {
logger.error("Failed to write cookies to local storage!");
return;
}
byte[] toEncrypt = bao.toByteArray();
SHA1Digest hash = new SHA1Digest();
hash.update(toEncrypt, 25, toEncrypt.length - 25);
hash.doFinal(toEncrypt, 5);
toEncrypt[1] = (byte)(totalUncompressedLen >>> 24);
toEncrypt[2] = (byte)(totalUncompressedLen >>> 16);
toEncrypt[3] = (byte)(totalUncompressedLen >>> 8);
toEncrypt[4] = (byte)(totalUncompressedLen & 0xFF);
if(key != null && key.length > 0) {
toEncrypt[0] = (byte)0x69;
AESLightEngine aes = new AESLightEngine();
aes.init(true, key);
int bs = aes.getBlockSize();
int blockCount = (toEncrypt.length % bs) != 0 ? (toEncrypt.length / bs + 1) : (toEncrypt.length / bs);
byte[] encrypted = new byte[blockCount * bs + 5];
encrypted[0] = (byte)2;
encrypted[1] = (byte)(toEncrypt.length >>> 24);
encrypted[2] = (byte)(toEncrypt.length >>> 16);
encrypted[3] = (byte)(toEncrypt.length >>> 8);
encrypted[4] = (byte)(toEncrypt.length & 0xFF);
byte[] cbcHelper = new byte[] { (byte) 29, (byte) 163, (byte) 4, (byte) 20, (byte) 207, (byte) 26,
(byte) 140, (byte) 55, (byte) 246, (byte) 250, (byte) 141, (byte) 183, (byte) 153, (byte) 154,
(byte) 59, (byte) 4 };
for(int i = 0; i < toEncrypt.length; i += bs) {
processBlockEncryptHelper(aes, toEncrypt, i, encrypted, 5 + i, bs, cbcHelper);
}
PlatformApplication.setLocalStorage(localStorageKey, encrypted, false);
}else {
toEncrypt[0] = (byte)1;
PlatformApplication.setLocalStorage(localStorageKey, toEncrypt, false);
}
}
}
private static void processBlockEncryptHelper(AESLightEngine aes, byte[] in, int inOff, byte[] out, int outOff,
int len, byte[] cbcHelper) {
int clampedBlockLength = Math.min(in.length - inOff, len);
if(clampedBlockLength == len) {
for(int i = 0; i < len; ++i) {
in[i + inOff] ^= cbcHelper[i];
}
aes.processBlock(in, inOff, out, outOff);
System.arraycopy(out, outOff, cbcHelper, 0, len);
}else {
byte[] paddedBlock = new byte[len];
System.arraycopy(in, inOff, paddedBlock, 0, clampedBlockLength);
byte padValue = (byte)(len - clampedBlockLength);
for(byte i = 0; i < padValue; ++i) {
paddedBlock[clampedBlockLength + i] = padValue;
}
for(int i = 0; i < len; ++i) {
paddedBlock[i] ^= cbcHelper[i];
}
aes.processBlock(paddedBlock, 0, out, outOff);
}
}
private static void processBlockDecryptHelper(AESLightEngine aes, byte[] in, int inOff, byte[] out, int outOff,
int paddedLen, int unpaddedLen, byte[] cbcHelper) throws IOException {
aes.processBlock(in, inOff, out, outOff);
for(int i = 0; i < paddedLen; ++i) {
out[i + outOff] ^= cbcHelper[i];
}
if(unpaddedLen == paddedLen) {
System.arraycopy(in, inOff, cbcHelper, 0, paddedLen);
}else {
byte padValue = (byte)(paddedLen - unpaddedLen);
for(byte i = 0; i < padValue; ++i) {
if(out[outOff + unpaddedLen + i] != padValue) {
throw new IOException("Invalid padding!");
}
}
}
}
}