mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-27 18:38:14 -05:00
Update #37 - Touch support without userscript, many other feats
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user