Update #51 - Protocol and FPS improvements, better workspace

This commit is contained in:
lax1dude
2025-05-18 15:01:06 -07:00
parent 71c61e33fd
commit 325a6826bf
1191 changed files with 9266 additions and 187695 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-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
@ -22,6 +22,7 @@ import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.client.StateFlags;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketGetOtherClientUUIDV4EAG;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
@ -53,7 +54,7 @@ public class ClientUUIDLoadingCache {
if(ret == null) {
Minecraft mc = Minecraft.getMinecraft();
if(mc != null && mc.thePlayer != null && mc.thePlayer.sendQueue.getEaglerMessageProtocol().ver >= 4) {
if(ignoreNonEaglerPlayers && !player.getGameProfile().getTextures().eaglerPlayer) {
if(StateFlags.eaglerPlayerFlag && player.getGameProfile().getTextures().eaglerPlayer != (byte) 2) {
ret = VANILLA_UUID;
}else {
ret = PENDING_UUID;
@ -86,7 +87,6 @@ public class ClientUUIDLoadingCache {
private static int requestId = 0;
private static long lastFlushReq = EagRuntime.steadyTimeMillis();
private static long lastFlushEvict = EagRuntime.steadyTimeMillis();
private static boolean ignoreNonEaglerPlayers = false;
public static void update() {
long timestamp = EagRuntime.steadyTimeMillis();
@ -122,19 +122,21 @@ public class ClientUUIDLoadingCache {
evictedUUIDs.clear();
}
private static final EaglercraftUUID MAGIC_DISABLE_NON_EAGLER_PLAYERS = new EaglercraftUUID(0xEEEEA64771094C4EL, 0x86E55B81D17E67EBL);
public static void handleResponse(int requestId, EaglercraftUUID clientId) {
WaitingLookup lookup = waitingIDs.remove(requestId);
if(lookup != null) {
lookup.player.clientBrandUUIDCache = clientId;
waitingUUIDs.remove(lookup.uuid);
}else {
if(requestId == -1 && MAGIC_DISABLE_NON_EAGLER_PLAYERS.equals(clientId)) {
ignoreNonEaglerPlayers = true;
}else {
logger.warn("Unsolicited client brand UUID lookup response #{} recieved! (Brand UUID: {})", requestId, clientId);
if (requestId == -1 && StateFlags.LEGACY_EAGLER_PLAYER_FLAG_PRESENT.equals(clientId)) {
Minecraft mc = Minecraft.getMinecraft();
if (mc != null && (mc.thePlayer == null || mc.thePlayer.sendQueue.getEaglerMessageProtocol().ver < 5)) {
StateFlags.eaglerPlayerFlag = true;
StateFlags.eaglerPlayerFlagSupervisor = true;
return;
}
}
logger.warn("Unsolicited client brand UUID lookup response #{} recieved! (Brand UUID: {})", requestId, clientId);
}
}
@ -146,10 +148,6 @@ public class ClientUUIDLoadingCache {
}
}
public static void resetFlags() {
ignoreNonEaglerPlayers = false;
}
private static class WaitingLookup {
private final int reqID;

View File

@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8;
import net.lax1dude.eaglercraft.v1_8.internal.ContextLostError;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
public class Display {
@ -83,6 +84,12 @@ public class Display {
return PlatformInput.contextLost();
}
public static void checkContextLost() {
if(PlatformInput.contextLost()) {
throw new ContextLostError();
}
}
public static boolean wasResized() {
return PlatformInput.wasResized();
}

View File

@ -69,10 +69,10 @@ public class EagRuntime {
operatingSystem = PlatformRuntime.getPlatformOS();
angleBackend = PlatformRuntime.getPlatformANGLE();
UpdateService.initialize();
EaglerXBungeeVersion.initialize();
EaglercraftGPU.warmUpCache();
ScreenRecordingController.initialize();
PlatformRuntime.postCreate();
Display.checkContextLost();
}
public static void destroy() {

View File

@ -1,91 +0,0 @@
/*
* 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.
*
*/
package net.lax1dude.eaglercraft.v1_8;
import org.json.JSONObject;
public class EaglerXBungeeVersion {
public static final String pluginFileEPK = "plugin_download.zip";
private static String pluginName = null;
private static String pluginVersion = null;
private static long pluginVersionLong = 0l;
private static String pluginButton = null;
private static String pluginFilename = null;
public static void initialize() {
String pluginVersionJson = EagRuntime.getRequiredResourceString("plugin_version.json");
JSONObject json = new JSONObject(pluginVersionJson);
pluginName = json.getString("pluginName");
pluginVersion = json.getString("pluginVersion");
pluginVersionLong = getVersionAsLong(pluginVersion);
pluginButton = json.getString("pluginButton");
pluginFilename = json.getString("pluginFilename");
}
public static String getPluginName() {
return pluginName;
}
public static String getPluginVersion() {
return pluginVersion;
}
public static long getPluginVersionLong() {
return pluginVersionLong;
}
public static String getPluginButton() {
return pluginButton;
}
public static String getPluginFilename() {
return pluginFilename;
}
public static long getVersionAsLong(String vers) {
try {
String[] verz = vers.split("\\.");
long ret = 0;
long div = 1000000000000l;
for(int i = 0; i < verz.length; ++i) {
ret += div * Long.parseLong(verz[i]);
div /= 10000l;
}
return ret;
}catch(Throwable t) {
return -1l;
}
}
public static byte[] getPluginDownload() {
return EagRuntime.getRequiredResourceBytes(pluginFileEPK);
}
public static void startPluginDownload() {
EagRuntime.downloadFileWithName(pluginFilename, getPluginDownload());
}
public static boolean isUpdateToPluginAvailable(String brand, String vers) {
if(pluginVersionLong == -1l || !pluginName.equals(brand)) {
return false;
}
long verz = getVersionAsLong(vers);
return verz != -1l && verz < pluginVersionLong;
}
}

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 = "u50";
public static final String projectForkVersion = "u51";
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 = "u50";
public static final String projectOriginVersion = "u51";
public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace
// EPK Version Identifier
public static final String EPKVersionIdentifier = "u50"; // Set to null to disable EPK version check
public static final String EPKVersionIdentifier = "u51"; // 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 = 50;
public static final int updateBundlePackageVersionInt = 51;
public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName;

View File

@ -150,8 +150,7 @@ public class PauseMenuCustomizeState {
case DISCORD_MODE_NONE:
default:
discordButtonText = null;
serverInfoURL = null;
serverInfoHash = null;
discordInviteURL = null;
break;
case DISCORD_MODE_INVITE_URL:
discordButtonText = packet.discordButtonText;

View File

@ -43,6 +43,7 @@ import java.util.List;
import com.google.common.collect.Lists;
import net.lax1dude.eaglercraft.v1_8.Base64;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom;
import net.lax1dude.eaglercraft.v1_8.crypto.GeneralDigest;
@ -108,6 +109,7 @@ public class HardwareFingerprint {
_wglShaderSource(vert, GLSLHeader.getVertexHeaderCompat(vshLocalSrc, DrawUtils.vertexShaderPrecision));
_wglCompileShader(vert);
if(_wglGetShaderi(vert, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
_wglDeleteShader(vert);
GlStateManager.deleteTexture(helperTexture);
return new byte[0];
@ -118,6 +120,7 @@ public class HardwareFingerprint {
_wglShaderSource(frag, GLSLHeader.getFragmentHeaderCompat(EagRuntime.getRequiredResourceString("/assets/eagler/glsl/hw_fingerprint.fsh"), shaderPrecision));
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
_wglDeleteShader(vert);
_wglDeleteShader(frag);
GlStateManager.deleteTexture(helperTexture);
@ -142,6 +145,7 @@ public class HardwareFingerprint {
_wglDeleteShader(frag);
if(_wglGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
_wglDeleteProgram(program);
GlStateManager.deleteTexture(helperTexture);
return new byte[0];

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* 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
@ -14,12 +14,12 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.internal;
import net.minecraft.network.PacketBuffer;
public class ContextLostError extends Error {
public interface IPluginMessageSendFunction {
public ContextLostError() {
super("WebGL context lost! Please refresh the page to continue");
}
void sendPluginMessage(String channel, PacketBuffer contents);
}
}

View File

@ -18,4 +18,10 @@ package net.lax1dude.eaglercraft.v1_8.internal;
public interface IVertexArrayGL extends IObjectGL {
int getBits();
void setBit(int bit);
void unsetBit(int bit);
}

View File

@ -55,10 +55,10 @@ public class MainMenuSkyboxTexture extends AbstractTexture {
GlStateManager.bindTexture(tex);
_wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
EaglercraftGPU.getNativeTexture(tex), 0);
_wglDrawBuffers(_GL_COLOR_ATTACHMENT0);
}else {
_wglBindFramebuffer(_GL_FRAMEBUFFER, framebuffer);
}
_wglDrawBuffers(new int[] { _GL_COLOR_ATTACHMENT0 });
}
public void deleteGlTexture() {

View File

@ -78,8 +78,10 @@ public class GameProfile {
}
public String toString() {
return (new ToStringBuilder(this)).append("id", this.id).append("name", this.name)
.append("legacy", false).toString();
return "GameProfile{id=" + this.id + ", name=" + this.name + ", legacy=false}";
//TODO: uncomment once JS runtime is updated to newer TeaVM version
//return (new ToStringBuilder(this)).append("id", this.id).append("name", this.name)
// .append("legacy", false).toString();
}
public boolean isLegacy() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 lax1dude. All Rights Reserved.
* Copyright (c) 2022-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
@ -22,48 +22,66 @@ import org.json.JSONObject;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.Base64;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.profile.SkinPackets;
public class TexturesProperty {
public final String skin;
public final String model;
public final SkinModel model;
public final String cape;
public final boolean eaglerPlayer;
public final byte eaglerPlayer;
public static final TexturesProperty defaultNull = new TexturesProperty(null, "default", null, false);
private EaglercraftUUID skinTextureUUID;
private TexturesProperty(String skin, String model, String cape, boolean eaglerPlayer) {
public static final TexturesProperty[] defaultNull = new TexturesProperty[] {
new TexturesProperty(null, SkinModel.STEVE, null, (byte) 0),
new TexturesProperty(null, SkinModel.STEVE, null, (byte) 1),
new TexturesProperty(null, SkinModel.STEVE, null, (byte) 2)
};
private TexturesProperty(String skin, SkinModel model, String cape, byte eaglerPlayer) {
this.skin = skin;
this.model = model;
this.cape = cape;
this.eaglerPlayer = eaglerPlayer;
}
public EaglercraftUUID loadSkinTextureUUID() {
if(skinTextureUUID == null && skin != null) {
skinTextureUUID = SkinPackets.createEaglerURLSkinUUID(skin);
}
return skinTextureUUID;
}
public static TexturesProperty parseProfile(GameProfile profile) {
String str = null;
byte isEagler = 0;
Property prop;
Collection<Property> etr = profile.getProperties().get("textures");
if(!etr.isEmpty()) {
Property prop = etr.iterator().next();
String str;
prop = etr.iterator().next();
try {
str = ArrayUtils.asciiString(Base64.decodeBase64(prop.getValue()));
}catch(Throwable t) {
return defaultNull;
}
boolean isEagler = false;
etr = profile.getProperties().get("isEaglerPlayer");
if(!etr.isEmpty()) {
prop = etr.iterator().next();
isEagler = prop.getValue().equalsIgnoreCase("true");
}
}
etr = profile.getProperties().get("isEaglerPlayer");
if(!etr.isEmpty()) {
prop = etr.iterator().next();
isEagler = prop.getValue().equalsIgnoreCase("true") ? (byte) 2 : (byte) 1;
}
if(str != null) {
return parseTextures(str, isEagler);
}else {
return defaultNull;
return defaultNull[isEagler];
}
}
public static TexturesProperty parseTextures(String string, boolean isEagler) {
public static TexturesProperty parseTextures(String string, byte isEagler) {
String skin = null;
String model = "default";
SkinModel model = SkinModel.STEVE;
String cape = null;
try {
JSONObject json = new JSONObject(string);
@ -74,7 +92,10 @@ public class TexturesProperty {
skin = skinObj.optString("url");
JSONObject meta = skinObj.optJSONObject("metadata");
if(meta != null) {
model = meta.optString("model", model);
String modelStr = meta.optString("model");
if(modelStr != null && modelStr.equalsIgnoreCase("slim")) {
model = SkinModel.STEVE;
}
}
}
JSONObject capeObj = json.optJSONObject("CAPE");

View File

@ -39,7 +39,6 @@ import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadg
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsRegisterV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsReleaseV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.IChatComponent;
@ -56,8 +55,8 @@ public class ServerNotificationManager {
private final TextureManager textureMgr;
protected int unreadCounter = 0;
public ServerNotificationManager() {
this.textureMgr = Minecraft.getMinecraft().getTextureManager();
public ServerNotificationManager(TextureManager textureMgr) {
this.textureMgr = textureMgr;
}
public void processPacketAddIcons(SPacketNotifIconsRegisterV4EAG packet) {

View File

@ -21,6 +21,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
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.IVertexArrayGL;
import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL;
@ -78,6 +79,7 @@ public class DrawUtils {
_wglCompileShader(vshLocal);
if(_wglGetShaderi(vshLocal, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
EaglercraftGPU.logger.error("Failed to compile GL_VERTEX_SHADER \"" + vertexShaderPath + "\"!");
String log = _wglGetShaderInfoLog(vshLocal);
if(log != null) {

View File

@ -47,6 +47,11 @@ public class EaglercraftGPU {
return _wglGenBuffers();
}
@Override
protected void invalidate(IBufferGL object) {
// Don't bother
}
@Override
protected void destroy(IBufferGL object) {
_wglDeleteBuffers(object);
@ -61,6 +66,11 @@ public class EaglercraftGPU {
return _wglGenBuffers();
}
@Override
protected void invalidate(IBufferGL object) {
// Don't bother
}
@Override
protected void destroy(IBufferGL object) {
_wglDeleteBuffers(object);
@ -68,13 +78,32 @@ public class EaglercraftGPU {
};
static final GLObjectRecycler<IVertexArrayGL> VAORecycler = new GLObjectRecycler<IVertexArrayGL>(128) {
static final GLObjectRecycler<IVertexArrayGL> VAORecycler = new GLObjectRecycler<IVertexArrayGL>(256) {
@Override
protected IVertexArrayGL create() {
return _wglGenVertexArrays();
}
@Override
protected void invalidate(IVertexArrayGL object) {
int i;
int bits = object.getBits();
if(bits != 0) {
IVertexArrayGL old = currentVertexArray;
if (old != object) {
_wglBindVertexArray(object);
}
do {
i = Integer.numberOfTrailingZeros(bits);
_wglDisableVertexAttribArray(i);
} while((bits &= ~((i << 1) - 1)) != 0);
if (old != object) {
_wglBindVertexArray(old);
}
}
}
@Override
protected void destroy(IVertexArrayGL object) {
_wglDeleteVertexArrays(object);
@ -186,7 +215,7 @@ public class EaglercraftGPU {
dp.bindQuad32 = false;
}
if(dp.vertexBuffer == null) {
dp.vertexBuffer = _wglGenBuffers();
dp.vertexBuffer = createGLArrayBuffer();
}
bindVAOGLArrayBufferNow(dp.vertexBuffer);
@ -250,7 +279,7 @@ public class EaglercraftGPU {
bindGLVertexArray(dp.vertexArray);
if(dp.mode == GL_QUADS) {
int cnt = dp.count;
if(cnt > 0xFFFF) {
if(cnt > quad16MaxVertices) {
if(!dp.bindQuad32) {
dp.bindQuad16 = false;
dp.bindQuad32 = true;
@ -258,16 +287,14 @@ public class EaglercraftGPU {
}else {
attachQuad32EmulationBuffer(cnt, false);
}
p.drawElements(GL_TRIANGLES, cnt + (cnt >> 1), GL_UNSIGNED_INT, 0);
p.drawElements(GL_TRIANGLES, (cnt >> 2) * 6, GL_UNSIGNED_INT, 0);
}else {
if(!dp.bindQuad16) {
dp.bindQuad16 = true;
dp.bindQuad32 = false;
attachQuad16EmulationBuffer(cnt, true);
}else {
attachQuad16EmulationBuffer(cnt, false);
attachQuad16EmulationBuffer(true);
}
p.drawElements(GL_TRIANGLES, cnt + (cnt >> 1), GL_UNSIGNED_SHORT, 0);
p.drawElements(GL_TRIANGLES, (cnt >> 2) * 6, GL_UNSIGNED_SHORT, 0);
}
}else {
p.drawArrays(dp.mode, 0, dp.count);
@ -436,7 +463,7 @@ public class EaglercraftGPU {
}
public static void destroyGLArrayBuffer(IBufferGL buffer) {
arrayBufferRecycler.destroy(buffer);
arrayBufferRecycler.destroyObject(buffer);
}
public static IBufferGL createGLElementArrayBuffer() {
@ -444,7 +471,7 @@ public class EaglercraftGPU {
}
public static void destroyGLElementArrayBuffer(IBufferGL buffer) {
elementArrayBufferRecycler.destroy(buffer);
elementArrayBufferRecycler.destroyObject(buffer);
}
public static boolean areVAOsEmulated() {
@ -461,7 +488,7 @@ public class EaglercraftGPU {
public static void destroyGLVertexArray(IVertexArrayGL buffer) {
if(!emulatedVAOs) {
VAORecycler.destroy(buffer);
VAORecycler.destroyObject(buffer);
}
}
@ -694,7 +721,7 @@ public class EaglercraftGPU {
public static final int CLEAR_BINDING_TEXTURE = 1;
public static final int CLEAR_BINDING_TEXTURE0 = 2;
public static final int CLEAR_BINDING_ACTIVE_TEXTURE = 4;
public static final int CLEAR_BINDING_BUFFER_ARRAY = 8;
public static final int CLEAR_BINDING_VERTEX_ARRAY = 8;
public static final int CLEAR_BINDING_ARRAY_BUFFER = 16;
public static final int CLEAR_BINDING_SHADER_PROGRAM = 32;
@ -712,7 +739,7 @@ public class EaglercraftGPU {
GlStateManager.activeTexture = 0;
_wglActiveTexture(GL_TEXTURE0);
}
if((mask & CLEAR_BINDING_BUFFER_ARRAY) != 0) {
if((mask & CLEAR_BINDING_VERTEX_ARRAY) != 0) {
currentVertexArray = null;
}
if((mask & CLEAR_BINDING_ARRAY_BUFFER) != 0) {
@ -757,11 +784,11 @@ public class EaglercraftGPU {
private static long lastRecyclerFlush = 0l;
public static void optimize() {
FixedFunctionPipeline.optimize();
long millis = EagRuntime.steadyTimeMillis();
if(millis - lastRecyclerFlush > 120000l) {
lastRecyclerFlush = millis;
arrayBufferRecycler.compact();
elementArrayBufferRecycler.compact();
VAORecycler.compact();
}
}
@ -778,34 +805,21 @@ public class EaglercraftGPU {
lastRender.update().drawDirectArrays(lastMode, 0, lastCount);
}
public static final int quad16MaxVertices = 65536;
private static IBufferGL quad16EmulationBuffer = null;
private static int quad16EmulationBufferSize = 0;
private static IBufferGL quad32EmulationBuffer = null;
private static int quad32EmulationBufferSize = 0;
public static void attachQuad16EmulationBuffer(int vertexCount, boolean bind) {
public static void attachQuad16EmulationBuffer(boolean bind) {
IBufferGL buf = quad16EmulationBuffer;
if(buf == null) {
quad16EmulationBuffer = buf = _wglGenBuffers();
int newSize = quad16EmulationBufferSize = (vertexCount & 0xFFFFF000) + 0x2000;
if(newSize > 0xFFFF) {
newSize = 0xFFFF;
}
EaglercraftGPU.bindVAOGLElementArrayBufferNow(buf);
resizeQuad16EmulationBuffer(newSize >> 2);
}else {
int cnt = quad16EmulationBufferSize;
if(cnt < vertexCount) {
int newSize = quad16EmulationBufferSize = (vertexCount & 0xFFFFF000) + 0x2000;
if(newSize > 0xFFFF) {
newSize = 0xFFFF;
}
EaglercraftGPU.bindVAOGLElementArrayBufferNow(buf);
resizeQuad16EmulationBuffer(newSize >> 2);
}else if(bind) {
EaglercraftGPU.bindVAOGLElementArrayBuffer(buf);
}
resizeQuad16EmulationBuffer(quad16MaxVertices >> 2);
}else if(bind) {
EaglercraftGPU.bindVAOGLElementArrayBuffer(buf);
}
}
@ -813,13 +827,13 @@ public class EaglercraftGPU {
IBufferGL buf = quad32EmulationBuffer;
if(buf == null) {
quad32EmulationBuffer = buf = _wglGenBuffers();
int newSize = quad32EmulationBufferSize = (vertexCount & 0xFFFFC000) + 0x8000;
int newSize = quad32EmulationBufferSize = (vertexCount + 0xFFFF) & 0xFFFF0000;
EaglercraftGPU.bindVAOGLElementArrayBufferNow(buf);
resizeQuad32EmulationBuffer(newSize >> 2);
}else {
int cnt = quad32EmulationBufferSize;
if(cnt < vertexCount) {
int newSize = quad32EmulationBufferSize = (vertexCount & 0xFFFFC000) + 0x8000;
int newSize = quad32EmulationBufferSize = (vertexCount + 0xFFFF) & 0xFFFF0000;
EaglercraftGPU.bindVAOGLElementArrayBufferNow(buf);
resizeQuad32EmulationBuffer(newSize >> 2);
}else if(bind) {

View File

@ -26,6 +26,9 @@ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
import net.lax1dude.eaglercraft.v1_8.Display;
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
@ -41,8 +44,9 @@ public class EffectPipelineFXAA {
private static final int _GL_RENDERBUFFER = 0x8D41;
private static final int _GL_COLOR_ATTACHMENT0 = 0x8CE0;
private static final int _GL_DEPTH_ATTACHMENT = 0x8D00;
private static final int _GL_DEPTH_COMPONENT16 = 0x81A5;
private static final int _GL_DEPTH_COMPONENT32F = 0x8CAC;
private static final int _GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
private static final int _GL_DEPTH_STENCIL = 0x84F9;
private static IProgramGL shaderProgram = null;
private static IUniformGL u_screenSize2f = null;
@ -63,6 +67,7 @@ public class EffectPipelineFXAA {
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile GL_FRAGMENT_SHADER \"" + fragmentShaderPath + "\" for EffectPipelineFXAA!");
String log = _wglGetShaderInfoLog(frag);
if(log != null) {
@ -91,6 +96,7 @@ public class EffectPipelineFXAA {
_wglDeleteShader(frag);
if(_wglGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to link shader program for EffectPipelineFXAA!");
String log = _wglGetProgramInfoLog(shaderProgram);
if(log != null) {
@ -121,8 +127,11 @@ public class EffectPipelineFXAA {
_wglBindRenderbuffer(_GL_RENDERBUFFER, framebufferDepth);
_wglBindFramebuffer(_GL_FRAMEBUFFER, framebuffer);
_wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, EaglercraftGPU.getNativeTexture(framebufferColor), 0);
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_DEPTH_ATTACHMENT, _GL_RENDERBUFFER, framebufferDepth);
_wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
EaglercraftGPU.getNativeTexture(framebufferColor), 0);
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER,
EaglercraftGPU.checkOpenGLESVersion() == 200 ? _GL_DEPTH_STENCIL_ATTACHMENT : _GL_DEPTH_ATTACHMENT,
_GL_RENDERBUFFER, framebufferDepth);
_wglBindFramebuffer(_GL_FRAMEBUFFER, null);
}
@ -136,7 +145,8 @@ public class EffectPipelineFXAA {
EaglercraftGPU.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null);
_wglBindRenderbuffer(_GL_RENDERBUFFER, framebufferDepth);
_wglRenderbufferStorage(_GL_RENDERBUFFER, EaglercraftGPU.checkOpenGLESVersion() == 200 ? _GL_DEPTH_COMPONENT16 : _GL_DEPTH_COMPONENT32F, width, height);
_wglRenderbufferStorage(_GL_RENDERBUFFER, EaglercraftGPU.checkOpenGLESVersion() == 200 ? _GL_DEPTH_STENCIL
: _GL_DEPTH_COMPONENT32F, width, height);
}
_wglBindFramebuffer(_GL_FRAMEBUFFER, framebuffer);

View File

@ -24,7 +24,7 @@ import java.util.List;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.IVertexArrayGL;
import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL;
@ -153,7 +153,7 @@ public class FixedFunctionPipeline {
EaglercraftGPU.bindGLShaderProgram(shaderProgram);
if(mode == GL_QUADS) {
StreamBufferInstance sb = currentVertexArray;
if(count > 0xFFFF) {
if(count > EaglercraftGPU.quad16MaxVertices) {
if(!sb.bindQuad32) {
sb.bindQuad16 = false;
sb.bindQuad32 = true;
@ -161,18 +161,14 @@ public class FixedFunctionPipeline {
}else {
EaglercraftGPU.attachQuad32EmulationBuffer(count, false);
}
EaglercraftGPU.drawElements(GL_TRIANGLES, count + (count >> 1),
GL_UNSIGNED_INT, 0);
EaglercraftGPU.drawElements(GL_TRIANGLES, (count >> 2) * 6, GL_UNSIGNED_INT, 0);
}else {
if(!sb.bindQuad16) {
sb.bindQuad16 = true;
sb.bindQuad32 = false;
EaglercraftGPU.attachQuad16EmulationBuffer(count, true);
}else {
EaglercraftGPU.attachQuad16EmulationBuffer(count, false);
EaglercraftGPU.attachQuad16EmulationBuffer(true);
}
EaglercraftGPU.drawElements(GL_TRIANGLES, count + (count >> 1),
GL_UNSIGNED_SHORT, 0);
EaglercraftGPU.drawElements(GL_TRIANGLES, (count >> 2) * 6, GL_UNSIGNED_SHORT, 0);
}
}else {
EaglercraftGPU.drawArrays(mode, offset, count);
@ -291,6 +287,7 @@ public class FixedFunctionPipeline {
_wglCompileShader(vsh);
if(_wglGetShaderi(vsh, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to compile GL_VERTEX_SHADER for state {} !", (visualizeBits(coreBits) + (enableExt && extBits != 0 ? " ext " + visualizeBits(extBits) : "")));
String log = _wglGetShaderInfoLog(vsh);
if(log != null) {
@ -309,6 +306,7 @@ public class FixedFunctionPipeline {
_wglCompileShader(fsh);
if(_wglGetShaderi(fsh, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to compile GL_FRAGMENT_SHADER for state {} !", (visualizeBits(coreBits) + (enableExt && extBits != 0 ? " ext " + visualizeBits(extBits) : "")));
String log = _wglGetShaderInfoLog(fsh);
if(log != null) {
@ -563,6 +561,7 @@ public class FixedFunctionPipeline {
_wglLinkProgram(compiledProg);
if(_wglGetProgrami(compiledProg, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Program could not be linked for state {} !", (visualizeBits(bits) + (extensionProvider != null && extBits != 0 ? " ext " + visualizeBits(extBits) : "")));
String log = _wglGetProgramInfoLog(compiledProg);
if(log != null) {
@ -574,8 +573,7 @@ public class FixedFunctionPipeline {
throw new IllegalStateException("Program could not be linked!");
}
streamBuffer = new StreamBuffer(FixedFunctionShader.initialSize, FixedFunctionShader.initialCount,
FixedFunctionShader.maxCount, (vertexArray, vertexBuffer) -> {
streamBuffer = new StreamBuffer((vertexArray, vertexBuffer) -> {
EaglercraftGPU.bindGLVertexArray(vertexArray);
EaglercraftGPU.bindVAOGLArrayBuffer(vertexBuffer);
@ -1063,12 +1061,6 @@ public class FixedFunctionPipeline {
return this;
}
static void optimize() {
for(int i = 0, l = pipelineListTracker.size(); i < l; ++i) {
pipelineListTracker.get(i).streamBuffer.optimize();
}
}
public static void flushCache() {
shaderSourceCacheVSH = null;
shaderSourceCacheFSH = null;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -18,10 +18,6 @@ package net.lax1dude.eaglercraft.v1_8.opengl;
public class FixedFunctionShader {
public static final int initialSize = 0x8000;
public static final int initialCount = 3;
public static final int maxCount = 8;
public class FixedFunctionState {
public static final int fixedFunctionStatesCount = 12;

View File

@ -40,6 +40,7 @@ public abstract class GLObjectRecycler<T> {
}
public void destroyObject(T obj) {
invalidate(obj);
deletedObjects.addLast(obj);
}
@ -51,6 +52,8 @@ public abstract class GLObjectRecycler<T> {
protected abstract T create();
protected abstract void invalidate(T object);
protected abstract void destroy(T object);
}

View File

@ -19,6 +19,7 @@ package net.lax1dude.eaglercraft.v1_8.opengl;
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.IVertexArrayGL;
import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL;
@ -91,6 +92,7 @@ public class InstancedFontRenderer {
_wglCompileShader(vert);
if(_wglGetShaderi(vert, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile GL_VERTEX_SHADER \"" + vertexShaderPath + "\" for InstancedFontRenderer!");
String log = _wglGetShaderInfoLog(vert);
if(log != null) {
@ -106,6 +108,7 @@ public class InstancedFontRenderer {
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile GL_FRAGMENT_SHADER \"" + fragmentShaderPath + "\" for InstancedFontRenderer!");
String log = _wglGetShaderInfoLog(frag);
if(log != null) {
@ -135,6 +138,7 @@ public class InstancedFontRenderer {
_wglDeleteShader(frag);
if(_wglGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to link shader program for InstancedFontRenderer!");
String log = _wglGetProgramInfoLog(shaderProgram);
if(log != null) {
@ -203,7 +207,7 @@ public class InstancedFontRenderer {
EaglercraftGPU.vertexAttribDivisor(0, 0);
EaglercraftGPU.bindVAOGLArrayBufferNow(instancesBuffer);
_wglBufferData(GL_ARRAY_BUFFER, fontDataBuffer.remaining(), GL_STREAM_DRAW);
_wglBufferData(GL_ARRAY_BUFFER, fontDataBuffer.capacity(), GL_STREAM_DRAW);
EaglercraftGPU.enableVertexAttribArray(1);
EaglercraftGPU.vertexAttribPointer(1, 2, GL_SHORT, false, 10, 0);
@ -377,6 +381,7 @@ public class InstancedFontRenderer {
int l = fontDataBuffer.limit();
fontDataBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, fontDataBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, fontDataBuffer);
fontDataBuffer.position(p);
@ -390,6 +395,7 @@ public class InstancedFontRenderer {
int l = fontBoldDataBuffer.limit();
fontBoldDataBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, fontBoldDataBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, fontBoldDataBuffer);
fontBoldDataBuffer.position(p);

View File

@ -19,6 +19,7 @@ package net.lax1dude.eaglercraft.v1_8.opengl;
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.IVertexArrayGL;
import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL;
@ -92,6 +93,7 @@ public class InstancedParticleRenderer {
_wglCompileShader(vert);
if(_wglGetShaderi(vert, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile GL_VERTEX_SHADER \"" + vertexShaderPath + "\" for InstancedParticleRenderer!");
String log = _wglGetShaderInfoLog(vert);
if(log != null) {
@ -107,6 +109,7 @@ public class InstancedParticleRenderer {
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile GL_FRAGMENT_SHADER \"" + fragmentShaderPath + "\" for InstancedParticleRenderer!");
String log = _wglGetShaderInfoLog(frag);
if(log != null) {
@ -136,6 +139,7 @@ public class InstancedParticleRenderer {
_wglDeleteShader(frag);
if(_wglGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to link shader program for InstancedParticleRenderer!");
String log = _wglGetProgramInfoLog(shaderProgram);
if(log != null) {
@ -184,7 +188,7 @@ public class InstancedParticleRenderer {
EaglercraftGPU.vertexAttribDivisor(0, 0);
EaglercraftGPU.bindVAOGLArrayBufferNow(instancesBuffer);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.remaining(), GL_STREAM_DRAW);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
EaglercraftGPU.enableVertexAttribArray(1);
EaglercraftGPU.vertexAttribPointer(1, 3, GL_FLOAT, false, 24, 0);
@ -311,6 +315,7 @@ public class InstancedParticleRenderer {
int l = particleBuffer.limit();
particleBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, particleBuffer);
particleBuffer.position(p);

View File

@ -223,4 +223,17 @@ class SoftGLVertexArray implements IVertexArrayGL {
}
@Override
public int getBits() {
return enabled;
}
@Override
public void setBit(int bit) {
}
@Override
public void unsetBit(int bit) {
}
}

View File

@ -22,6 +22,7 @@ import java.util.List;
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL;
import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
@ -78,6 +79,7 @@ public class SpriteLevelMixer {
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to compile GL_FRAGMENT_SHADER \"" + fragmentShaderPath + "\" for SpriteLevelMixer!");
String log = _wglGetShaderInfoLog(frag);
if(log != null) {
@ -106,6 +108,7 @@ public class SpriteLevelMixer {
_wglDeleteShader(frag);
if(_wglGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to link shader program for SpriteLevelMixer!");
String log = _wglGetProgramInfoLog(shaderProgram);
if(log != null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024 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
@ -24,11 +24,7 @@ import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
public class StreamBuffer {
public static final int poolSize = 16;
public final int initialSize;
public final int initialCount;
public final int maxCount;
public static final int poolSize = 4;
protected static final PoolInstance[] pool = new PoolInstance[poolSize];
protected static int poolBufferID = 0;
@ -55,21 +51,23 @@ public class StreamBuffer {
}
private static void resizeInstance(PoolInstance instance, int requiredMemory) {
if(instance.vertexBuffer == null) {
instance.vertexBuffer = _wglGenBuffers();
IBufferGL buffer = instance.vertexBuffer;
if (buffer == null) {
buffer = _wglGenBuffers();
instance.vertexBuffer = buffer;
}
if(instance.vertexBufferSize < requiredMemory) {
int newSize = (requiredMemory & 0xFFFFF000) + 0x2000;
EaglercraftGPU.bindGLArrayBuffer(instance.vertexBuffer);
_wglBufferData(GL_ARRAY_BUFFER, newSize, GL_STREAM_DRAW);
int newSize = instance.vertexBufferSize;
if (newSize < requiredMemory) {
newSize = (requiredMemory + 0xFFFF) & 0xFFFF0000;
instance.vertexBufferSize = newSize;
}
EaglercraftGPU.bindGLArrayBuffer(buffer);
_wglBufferData(GL_ARRAY_BUFFER, newSize, GL_STREAM_DRAW);
}
protected StreamBufferInstance[] buffers;
protected int currentBufferId = 0;
protected int overflowCounter = 0;
protected final IStreamBufferInitializer initializer;
@ -95,19 +93,20 @@ public class StreamBuffer {
void initialize(IVertexArrayGL vertexArray, IBufferGL vertexBuffer);
}
public StreamBuffer(int initialSize, int initialCount, int maxCount, IStreamBufferInitializer initializer) {
if(maxCount > poolSize) {
maxCount = poolSize;
public StreamBuffer(IStreamBufferInitializer initializer) {
this(poolSize, initializer);
}
public StreamBuffer(int count, IStreamBufferInitializer initializer) {
if(count > poolSize) {
count = poolSize;
}
this.buffers = new StreamBufferInstance[initialCount];
this.buffers = new StreamBufferInstance[count];
for(int i = 0; i < this.buffers.length; ++i) {
StreamBufferInstance j = new StreamBufferInstance();
j.poolInstance = fillPoolInstance();
this.buffers[i] = j;
}
this.initialSize = initialSize;
this.initialCount = initialCount;
this.maxCount = maxCount;
this.initializer = initializer;
}
@ -121,67 +120,6 @@ public class StreamBuffer {
return next;
}
public void optimize() {
overflowCounter += currentBufferId - buffers.length;
if(overflowCounter < -25) {
int newCount = buffers.length - 1 + ((overflowCounter + 25) / 5);
if(newCount < initialCount) {
newCount = initialCount;
}
if(newCount < buffers.length) {
StreamBufferInstance[] newArray = new StreamBufferInstance[newCount];
for(int i = 0; i < buffers.length; ++i) {
if(i < newArray.length) {
newArray[i] = buffers[i];
}else {
if(buffers[i].vertexArray != null) {
EaglercraftGPU.destroyGLVertexArray(buffers[i].vertexArray);
}
}
}
buffers = newArray;
refill();
}
overflowCounter = 0;
}else if(overflowCounter > 15) {
int newCount = buffers.length + 1 + ((overflowCounter - 15) / 5);
if(newCount > maxCount) {
newCount = maxCount;
}
if(newCount > buffers.length) {
StreamBufferInstance[] newArray = new StreamBufferInstance[newCount];
for(int i = 0; i < newArray.length; ++i) {
if(i < buffers.length) {
newArray[i] = buffers[i];
}else {
newArray[i] = new StreamBufferInstance();
}
}
buffers = newArray;
refill();
}
overflowCounter = 0;
}
currentBufferId = 0;
}
private void refill() {
for(int i = 0; i < buffers.length; ++i) {
PoolInstance j = fillPoolInstance();
StreamBufferInstance k = buffers[i];
if(j != k.poolInstance) {
PoolInstance l = k.poolInstance;
k.poolInstance = j;
if(k.vertexArray != null) {
if(j.vertexBuffer == null) {
resizeInstance(j, l.vertexBufferSize);
}
initializer.initialize(k.vertexArray, j.vertexBuffer);
}
}
}
}
public void destroy() {
for(int i = 0; i < buffers.length; ++i) {
StreamBufferInstance next = buffers[i];
@ -189,12 +127,6 @@ public class StreamBuffer {
EaglercraftGPU.destroyGLVertexArray(next.vertexArray);
}
}
buffers = new StreamBufferInstance[initialCount];
for(int i = 0; i < initialCount; ++i) {
StreamBufferInstance j = new StreamBufferInstance();
j.poolInstance = fillPoolInstance();
buffers[i] = j;
}
}
public static void destroyPool() {

View File

@ -21,6 +21,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
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.IProgramGL;
import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
@ -100,6 +101,7 @@ public class TextureCopyUtil {
_wglCompileShader(vshShader);
if(_wglGetShaderi(vshShader, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to compile GL_VERTEX_SHADER \"" + vertexShaderPath + "\" for TextureCopyUtil!");
String log = _wglGetShaderInfoLog(vshShader);
if(log != null) {
@ -126,6 +128,7 @@ public class TextureCopyUtil {
_wglCompileShader(frag);
if(_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to compile GL_FRAGMENT_SHADER \"" + fragmentShaderPath + "\" for TextureCopyUtil!");
String log = _wglGetShaderInfoLog(frag);
if(log != null) {
@ -154,6 +157,7 @@ public class TextureCopyUtil {
_wglDeleteShader(frag);
if(_wglGetProgrami(shaderProgram, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
LOGGER.error("Failed to link shader program for TextureCopyUtil!");
String log = _wglGetProgramInfoLog(shaderProgram);
if(log != null) {

View File

@ -1483,6 +1483,7 @@ public class EaglerDeferredPipeline {
GlStateManager.globalEnableBlend();
GlStateManager.enableBlend();
GlStateManager.depthMask(false);
GlStateManager.cullFace(GL_FRONT);
GlStateManager.tryBlendFuncSeparate(GL_ZERO, GL_SRC_COLOR, GL_ZERO, GL_ZERO);
GlStateManager.enablePolygonOffset();
GlStateManager.doPolygonOffset(0.25f, 1.0f);
@ -1497,6 +1498,7 @@ public class EaglerDeferredPipeline {
GlStateManager.disableBlend();
GlStateManager.globalDisableBlend();
GlStateManager.depthMask(true);
GlStateManager.cullFace(GL_BACK);
GlStateManager.disablePolygonOffset();
GlStateManager.colorMask(false, false, false, false);
DeferredStateManager.checkGLError("Post: endDrawColoredShadows()");

View File

@ -92,7 +92,7 @@ public class ForwardAcceleratedEffectRenderer extends AbstractAcceleratedEffectR
_wglVertexAttribDivisor(0, 0);
EaglercraftGPU.bindGLArrayBuffer(instancesBuffer);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.remaining(), GL_STREAM_DRAW);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglEnableVertexAttribArray(1);
_wglVertexAttribPointer(1, 3, GL_FLOAT, false, 24, 0);
@ -149,6 +149,7 @@ public class ForwardAcceleratedEffectRenderer extends AbstractAcceleratedEffectR
int l = particleBuffer.limit();
particleBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, particleBuffer);
particleBuffer.position(p);

View File

@ -92,7 +92,7 @@ public class GBufferAcceleratedEffectRenderer extends AbstractAcceleratedEffectR
_wglVertexAttribDivisor(0, 0);
EaglercraftGPU.bindGLArrayBuffer(instancesBuffer);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.remaining(), GL_STREAM_DRAW);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglEnableVertexAttribArray(1);
_wglVertexAttribPointer(1, 3, GL_FLOAT, false, 24, 0);
@ -148,6 +148,7 @@ public class GBufferAcceleratedEffectRenderer extends AbstractAcceleratedEffectR
int l = particleBuffer.limit();
particleBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, particleBuffer);
particleBuffer.position(p);

View File

@ -89,7 +89,7 @@ public class LensFlareMeshRenderer {
streaksVertexArray = _wglGenVertexArrays();
EaglercraftGPU.bindGLVertexArray(streaksVertexArray);
EaglercraftGPU.attachQuad16EmulationBuffer(16, true);
EaglercraftGPU.attachQuad16EmulationBuffer(true);
_wglEnableVertexAttribArray(0);
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 16, 0);

View File

@ -22,6 +22,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
import java.util.Arrays;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL;
import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
@ -69,6 +70,7 @@ public class ShaderCompiler {
_wglCompileShader(ret);
if(_wglGetShaderi(ret, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to compile {} \"{}\" of program \"{}\"!", getStageName(stage), filename, name);
String log = _wglGetShaderInfoLog(ret);
if(log != null) {
@ -95,6 +97,7 @@ public class ShaderCompiler {
_wglDetachShader(ret, frag);
if(_wglGetProgrami(ret, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.error("Failed to link program \"{}\"!", name);
String log = _wglGetProgramInfoLog(ret);
if(log != null) {

View File

@ -97,7 +97,7 @@ public class DynamicLightsAcceleratedEffectRenderer extends AbstractAcceleratedE
_wglVertexAttribDivisor(0, 0);
EaglercraftGPU.bindGLArrayBuffer(instancesBuffer);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.remaining(), GL_STREAM_DRAW);
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglEnableVertexAttribArray(1);
_wglVertexAttribPointer(1, 3, GL_FLOAT, false, 24, 0);
@ -155,6 +155,7 @@ public class DynamicLightsAcceleratedEffectRenderer extends AbstractAcceleratedE
int l = particleBuffer.limit();
particleBuffer.flip();
_wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW);
_wglBufferSubData(GL_ARRAY_BUFFER, 0, particleBuffer);
particleBuffer.position(p);

View File

@ -210,7 +210,6 @@ public class EaglerProfile {
public static void handleForceSkinPreset(int preset) {
isServerSkinOverride = true;
overridePresetSkinId = preset;
ServerSkinCache.needReloadClientSkin = true;
}
public static void handleForceSkinCustom(int modelID, byte[] datav3) {
@ -229,13 +228,11 @@ public class EaglerProfile {
}else {
overrideCustomSkin.copyPixelsIn(datav3);
}
ServerSkinCache.needReloadClientSkin = true;
}
public static void handleForceCapePreset(int preset) {
isServerCapeOverride = true;
overridePresetCapeId = preset;
ServerCapeCache.needReloadClientCape = true;
}
public static void handleForceCapeCustom(byte[] custom) {
@ -252,7 +249,6 @@ public class EaglerProfile {
}else {
overrideCustomCape.copyPixelsIn(pixels32x32);
}
ServerCapeCache.needReloadClientCape = true;
}
public static void clearServerSkinOverride() {

View File

@ -19,7 +19,7 @@ package net.lax1dude.eaglercraft.v1_8.profile;
import net.lax1dude.eaglercraft.v1_8.Keyboard;
import net.lax1dude.eaglercraft.v1_8.internal.KeyboardConstants;
import net.lax1dude.eaglercraft.v1_8.minecraft.EnumInputEvent;
import net.lax1dude.eaglercraft.v1_8.socket.ConnectionHandshake;
import net.lax1dude.eaglercraft.v1_8.socket.GuiHandshakeApprove;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
@ -64,7 +64,7 @@ public class GuiAuthenticationScreen extends GuiScreen {
public void initGui() {
if(authTypeForWarning != Integer.MAX_VALUE) {
GuiScreen scr = ConnectionHandshake.displayAuthProtocolConfirm(authTypeForWarning, parent, this);
GuiScreen scr = GuiHandshakeApprove.displayAuthProtocolConfirm(authTypeForWarning, parent, this);
authTypeForWarning = Integer.MAX_VALUE;
if(scr != null) {
mc.displayGuiScreen(scr);
@ -90,7 +90,7 @@ public class GuiAuthenticationScreen extends GuiScreen {
protected void actionPerformed(GuiButton parGuiButton) {
if(parGuiButton.id == 1) {
this.mc.displayGuiScreen(new GuiConnecting(retAfterAuthScreen, password.getText()));
this.mc.displayGuiScreen(new GuiConnecting(retAfterAuthScreen, password.getText(), allowPlaintext));
}else {
this.mc.displayGuiScreen(parent);
}

View File

@ -1,250 +0,0 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.profile;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketGetOtherCapeEAG;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
public class ServerCapeCache {
private static final Logger logger = LogManager.getLogger("ServerCapeCache");
public class CapeCacheEntry {
protected final boolean isPresetCape;
protected final int presetCapeId;
protected final CacheCustomCape customCape;
protected long lastCacheHit = EagRuntime.steadyTimeMillis();
protected CapeCacheEntry(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation) {
this.isPresetCape = false;
this.presetCapeId = -1;
this.customCape = new CacheCustomCape(textureInstance, resourceLocation);
ServerCapeCache.this.textureManager.loadTexture(resourceLocation, textureInstance);
}
/**
* Use only for the constant for the client player
*/
protected CapeCacheEntry(ResourceLocation resourceLocation) {
this.isPresetCape = false;
this.presetCapeId = -1;
this.customCape = new CacheCustomCape(null, resourceLocation);
}
protected CapeCacheEntry(int presetSkinId) {
this.isPresetCape = true;
this.presetCapeId = presetSkinId;
this.customCape = null;
}
public ResourceLocation getResourceLocation() {
if(isPresetCape) {
return DefaultCapes.getCapeFromId(presetCapeId).location;
}else {
if(customCape != null) {
return customCape.resourceLocation;
}else {
return null;
}
}
}
protected void free() {
if(!isPresetCape && customCape.resourceLocation != null) {
ServerCapeCache.this.textureManager.deleteTexture(customCape.resourceLocation);
}
}
}
protected static class CacheCustomCape {
protected final EaglerSkinTexture textureInstance;
protected final ResourceLocation resourceLocation;
protected CacheCustomCape(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation) {
this.textureInstance = textureInstance;
this.resourceLocation = resourceLocation;
}
}
private final CapeCacheEntry defaultCacheEntry = new CapeCacheEntry(0);
private final Map<EaglercraftUUID, CapeCacheEntry> capesCache = new HashMap<>();
private final Map<EaglercraftUUID, Long> waitingCapes = new HashMap<>();
private final Map<EaglercraftUUID, Long> evictedCapes = new HashMap<>();
private final NetHandlerPlayClient netHandler;
protected final TextureManager textureManager;
private final EaglercraftUUID clientPlayerId;
private CapeCacheEntry clientPlayerCacheEntry;
private long lastFlush = EagRuntime.steadyTimeMillis();
private long lastFlushReq = EagRuntime.steadyTimeMillis();
private long lastFlushEvict = EagRuntime.steadyTimeMillis();
private static int texId = 0;
public static boolean needReloadClientCape = false;
public ServerCapeCache(NetHandlerPlayClient netHandler, TextureManager textureManager) {
this.netHandler = netHandler;
this.textureManager = textureManager;
this.clientPlayerId = EaglerProfile.getPlayerUUID();
reloadClientPlayerCape();
}
public void reloadClientPlayerCape() {
needReloadClientCape = false;
this.clientPlayerCacheEntry = new CapeCacheEntry(EaglerProfile.getActiveCapeResourceLocation());
}
public CapeCacheEntry getClientPlayerCape() {
return clientPlayerCacheEntry;
}
public CapeCacheEntry getCape(EaglercraftUUID player) {
if(player.equals(clientPlayerId)) {
return clientPlayerCacheEntry;
}
CapeCacheEntry etr = capesCache.get(player);
if(etr == null) {
if(!waitingCapes.containsKey(player) && !evictedCapes.containsKey(player)) {
waitingCapes.put(player, EagRuntime.steadyTimeMillis());
netHandler.sendEaglerMessage(new CPacketGetOtherCapeEAG(player.msb, player.lsb));
}
return defaultCacheEntry;
}else {
etr.lastCacheHit = EagRuntime.steadyTimeMillis();
return etr;
}
}
public void cacheCapePreset(EaglercraftUUID player, int presetId) {
if(waitingCapes.remove(player) != null) {
CapeCacheEntry etr = capesCache.remove(player);
if(etr != null) {
etr.free();
}
capesCache.put(player, new CapeCacheEntry(presetId));
}else {
logger.error("Unsolicited cape response recieved for \"{}\"! (preset {})", player, presetId);
}
}
public void cacheCapeCustom(EaglercraftUUID player, byte[] pixels) {
if(waitingCapes.remove(player) != null) {
CapeCacheEntry etr = capesCache.remove(player);
if(etr != null) {
etr.free();
}
byte[] pixels32x32 = new byte[4096];
SkinConverter.convertCape23x17RGBto32x32RGBA(pixels, pixels32x32);
try {
etr = new CapeCacheEntry(new EaglerSkinTexture(pixels32x32, 32, 32),
new ResourceLocation("eagler:capes/multiplayer/tex_" + texId++));
}catch(Throwable t) {
etr = new CapeCacheEntry(0);
logger.error("Could not process custom skin packet for \"{}\"!", player);
logger.error(t);
}
capesCache.put(player, etr);
}else {
logger.error("Unsolicited skin response recieved for \"{}\"!", player);
}
}
public void flush() {
long millis = EagRuntime.steadyTimeMillis();
if(millis - lastFlushReq > 5000l) {
lastFlushReq = millis;
if(!waitingCapes.isEmpty()) {
Iterator<Long> waitingItr = waitingCapes.values().iterator();
while(waitingItr.hasNext()) {
if(millis - waitingItr.next().longValue() > 30000l) {
waitingItr.remove();
}
}
}
}
if(millis - lastFlushEvict > 1000l) {
lastFlushEvict = millis;
if(!evictedCapes.isEmpty()) {
Iterator<Long> evictItr = evictedCapes.values().iterator();
while(evictItr.hasNext()) {
if(millis - evictItr.next().longValue() > 3000l) {
evictItr.remove();
}
}
}
}
if(millis - lastFlush > 60000l) {
lastFlush = millis;
if(!capesCache.isEmpty()) {
Iterator<CapeCacheEntry> entryItr = capesCache.values().iterator();
while(entryItr.hasNext()) {
CapeCacheEntry etr = entryItr.next();
if(millis - etr.lastCacheHit > 900000l) { // 15 minutes
entryItr.remove();
etr.free();
}
}
}
}
if(needReloadClientCape) {
reloadClientPlayerCape();
}
}
public void destroy() {
Iterator<CapeCacheEntry> entryItr = capesCache.values().iterator();
while(entryItr.hasNext()) {
entryItr.next().free();
}
capesCache.clear();
waitingCapes.clear();
evictedCapes.clear();
}
public void evictCape(EaglercraftUUID uuid) {
evictedCapes.put(uuid, Long.valueOf(EagRuntime.steadyTimeMillis()));
CapeCacheEntry etr = capesCache.remove(uuid);
if(etr != null) {
etr.free();
}
}
public void handleInvalidate(EaglercraftUUID uuid) {
CapeCacheEntry etr = capesCache.remove(uuid);
if(etr != null) {
etr.free();
}
}
}

View File

@ -1,336 +0,0 @@
/*
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.profile;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.mojang.authlib.GameProfile;
import net.lax1dude.eaglercraft.v1_8.mojang.authlib.TexturesProperty;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketGetOtherSkinEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketGetSkinByURLEAG;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
public class ServerSkinCache {
private static final Logger logger = LogManager.getLogger("ServerSkinCache");
public class SkinCacheEntry {
protected final boolean isPresetSkin;
protected final int presetSkinId;
protected final CacheCustomSkin customSkin;
protected long lastCacheHit = EagRuntime.steadyTimeMillis();
protected SkinCacheEntry(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation, SkinModel model) {
this.isPresetSkin = false;
this.presetSkinId = -1;
this.customSkin = new CacheCustomSkin(textureInstance, resourceLocation, model);
ServerSkinCache.this.textureManager.loadTexture(resourceLocation, textureInstance);
}
/**
* Use only for the constant for the client player
*/
protected SkinCacheEntry(ResourceLocation resourceLocation, SkinModel model) {
this.isPresetSkin = false;
this.presetSkinId = -1;
this.customSkin = new CacheCustomSkin(null, resourceLocation, model);
}
protected SkinCacheEntry(int presetSkinId) {
this.isPresetSkin = true;
this.presetSkinId = presetSkinId;
this.customSkin = null;
}
public ResourceLocation getResourceLocation() {
if(isPresetSkin) {
return DefaultSkins.getSkinFromId(presetSkinId).location;
}else {
if(customSkin != null) {
return customSkin.resourceLocation;
}else {
return DefaultSkins.DEFAULT_STEVE.location;
}
}
}
public SkinModel getSkinModel() {
if(isPresetSkin) {
return DefaultSkins.getSkinFromId(presetSkinId).model;
}else {
if(customSkin != null) {
return customSkin.model;
}else {
return DefaultSkins.DEFAULT_STEVE.model;
}
}
}
protected void free() {
if(!isPresetSkin) {
ServerSkinCache.this.textureManager.deleteTexture(customSkin.resourceLocation);
}
}
}
protected static class CacheCustomSkin {
protected final EaglerSkinTexture textureInstance;
protected final ResourceLocation resourceLocation;
protected final SkinModel model;
protected CacheCustomSkin(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation, SkinModel model) {
this.textureInstance = textureInstance;
this.resourceLocation = resourceLocation;
this.model = model;
}
}
protected static class WaitingSkin {
protected final long timeout;
protected final SkinModel model;
protected WaitingSkin(long timeout, SkinModel model) {
this.timeout = timeout;
this.model = model;
}
}
private final SkinCacheEntry defaultCacheEntry = new SkinCacheEntry(0);
private final SkinCacheEntry defaultSlimCacheEntry = new SkinCacheEntry(1);
private final Map<EaglercraftUUID, SkinCacheEntry> skinsCache = new HashMap<>();
private final Map<EaglercraftUUID, WaitingSkin> waitingSkins = new HashMap<>();
private final Map<EaglercraftUUID, Long> evictedSkins = new HashMap<>();
private final NetHandlerPlayClient netHandler;
protected final TextureManager textureManager;
private final EaglercraftUUID clientPlayerId;
private SkinCacheEntry clientPlayerCacheEntry;
private long lastFlush = EagRuntime.steadyTimeMillis();
private long lastFlushReq = EagRuntime.steadyTimeMillis();
private long lastFlushEvict = EagRuntime.steadyTimeMillis();
private static int texId = 0;
public static boolean needReloadClientSkin = false;
public ServerSkinCache(NetHandlerPlayClient netHandler, TextureManager textureManager) {
this.netHandler = netHandler;
this.textureManager = textureManager;
this.clientPlayerId = EaglerProfile.getPlayerUUID();
reloadClientPlayerSkin();
}
public void reloadClientPlayerSkin() {
needReloadClientSkin = false;
this.clientPlayerCacheEntry = new SkinCacheEntry(EaglerProfile.getActiveSkinResourceLocation(), EaglerProfile.getActiveSkinModel());
}
public SkinCacheEntry getClientPlayerSkin() {
return clientPlayerCacheEntry;
}
public SkinCacheEntry getSkin(GameProfile player) {
EaglercraftUUID uuid = player.getId();
if(uuid != null && uuid.equals(clientPlayerId)) {
return clientPlayerCacheEntry;
}
TexturesProperty props = player.getTextures();
if(props.eaglerPlayer || props.skin == null) {
if(uuid != null) {
return _getSkin(uuid);
}else {
if("slim".equalsIgnoreCase(props.model)) {
return defaultSlimCacheEntry;
}else {
return defaultCacheEntry;
}
}
}else {
return getSkin(props.skin, SkinModel.getModelFromId(props.model));
}
}
public SkinCacheEntry getSkin(EaglercraftUUID player) {
if(player.equals(clientPlayerId)) {
return clientPlayerCacheEntry;
}
return _getSkin(player);
}
private SkinCacheEntry _getSkin(EaglercraftUUID player) {
SkinCacheEntry etr = skinsCache.get(player);
if(etr == null) {
if(!waitingSkins.containsKey(player) && !evictedSkins.containsKey(player)) {
waitingSkins.put(player, new WaitingSkin(EagRuntime.steadyTimeMillis(), null));
netHandler.sendEaglerMessage(new CPacketGetOtherSkinEAG(player.msb, player.lsb));
}
return defaultCacheEntry;
}else {
etr.lastCacheHit = EagRuntime.steadyTimeMillis();
return etr;
}
}
public SkinCacheEntry getSkin(String url, SkinModel skinModelResponse) {
if(url.length() > 0x7F00) {
return skinModelResponse == SkinModel.ALEX ? defaultSlimCacheEntry : defaultCacheEntry;
}
EaglercraftUUID generatedUUID = SkinPackets.createEaglerURLSkinUUID(url);
SkinCacheEntry etr = skinsCache.get(generatedUUID);
if(etr != null) {
etr.lastCacheHit = EagRuntime.steadyTimeMillis();
return etr;
}else {
if(!waitingSkins.containsKey(generatedUUID) && !evictedSkins.containsKey(generatedUUID)) {
waitingSkins.put(generatedUUID, new WaitingSkin(EagRuntime.steadyTimeMillis(), skinModelResponse));
netHandler.sendEaglerMessage(new CPacketGetSkinByURLEAG(generatedUUID.msb, generatedUUID.lsb, url));
}
}
return skinModelResponse == SkinModel.ALEX ? defaultSlimCacheEntry : defaultCacheEntry;
}
public void cacheSkinPreset(EaglercraftUUID player, int presetId) {
if(waitingSkins.remove(player) != null) {
SkinCacheEntry etr = skinsCache.remove(player);
if(etr != null) {
etr.free();
}
skinsCache.put(player, new SkinCacheEntry(presetId));
}else {
logger.error("Unsolicited skin response recieved for \"{}\"! (preset {})", player, presetId);
}
}
public void cacheSkinCustom(EaglercraftUUID player, byte[] pixels, SkinModel model) {
WaitingSkin waitingSkin;
if((waitingSkin = waitingSkins.remove(player)) != null) {
SkinCacheEntry etr = skinsCache.remove(player);
if(etr != null) {
etr.free();
}
if(waitingSkin.model != null) {
model = waitingSkin.model;
}else if(model == null) {
model = (player.hashCode() & 1) != 0 ? SkinModel.ALEX : SkinModel.STEVE;
}
try {
etr = new SkinCacheEntry(new EaglerSkinTexture(pixels, model.width, model.height),
new ResourceLocation("eagler:skins/multiplayer/tex_" + texId++), model);
}catch(Throwable t) {
etr = new SkinCacheEntry(0);
logger.error("Could not process custom skin packet for \"{}\"!", player);
logger.error(t);
}
skinsCache.put(player, etr);
}else {
logger.error("Unsolicited skin response recieved for \"{}\"! (custom {}x{})", player, model.width, model.height);
}
}
public SkinModel getRequestedSkinType(EaglercraftUUID waiting) {
WaitingSkin waitingSkin;
if((waitingSkin = waitingSkins.get(waiting)) != null) {
return waitingSkin.model;
}else {
return null;
}
}
public void flush() {
long millis = EagRuntime.steadyTimeMillis();
if(millis - lastFlushReq > 5000l) {
lastFlushReq = millis;
if(!waitingSkins.isEmpty()) {
Iterator<WaitingSkin> waitingItr = waitingSkins.values().iterator();
while(waitingItr.hasNext()) {
if(millis - waitingItr.next().timeout > 20000l) {
waitingItr.remove();
}
}
}
}
if(millis - lastFlushEvict > 1000l) {
lastFlushEvict = millis;
if(!evictedSkins.isEmpty()) {
Iterator<Long> evictItr = evictedSkins.values().iterator();
while(evictItr.hasNext()) {
if(millis - evictItr.next().longValue() > 3000l) {
evictItr.remove();
}
}
}
}
if(millis - lastFlush > 60000l) {
lastFlush = millis;
if(!skinsCache.isEmpty()) {
Iterator<SkinCacheEntry> entryItr = skinsCache.values().iterator();
while(entryItr.hasNext()) {
SkinCacheEntry etr = entryItr.next();
if(millis - etr.lastCacheHit > 900000l) { // 15 minutes
entryItr.remove();
etr.free();
}
}
}
}
if(needReloadClientSkin) {
reloadClientPlayerSkin();
}
}
public void destroy() {
Iterator<SkinCacheEntry> entryItr = skinsCache.values().iterator();
while(entryItr.hasNext()) {
entryItr.next().free();
}
skinsCache.clear();
waitingSkins.clear();
evictedSkins.clear();
}
public void evictSkin(EaglercraftUUID uuid) {
evictedSkins.put(uuid, Long.valueOf(EagRuntime.steadyTimeMillis()));
SkinCacheEntry etr = skinsCache.remove(uuid);
if(etr != null) {
etr.free();
}
}
public void handleInvalidate(EaglercraftUUID uuid) {
SkinCacheEntry etr = skinsCache.remove(uuid);
if(etr != null) {
etr.free();
}
}
}

View File

@ -72,6 +72,14 @@ public enum SkinModel {
return STEVE;
}
}
public static SkinModel getSanitizedModelFromId(int id) {
SkinModel ret = getModelFromId(id & 0x7F);
if((id & 0x80) != 0 && ret.sanitize) {
ret = STEVE;
}
return ret;
}
static {
SkinModel[] arr = values();

View File

@ -0,0 +1,111 @@
/*
* 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.skin_cache;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.DefaultSkins;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerSkinTexture;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
class ForeignTextureEntry extends SkinData {
protected final EaglercraftUUID uuid;
protected final String url;
protected int state;
protected long lastHit;
protected ResourceLocation skinLocation;
protected EaglerSkinTexture skinTexture;
protected SkinModel skinModel;
protected SkinData inverseModel;
public ForeignTextureEntry(EaglercraftUUID uuid, String url, SkinModel skinModel) {
this.uuid = uuid;
this.url = url;
this.skinModel = skinModel;
}
@Override
public ResourceLocation getLocation() {
return skinLocation;
}
@Override
public SkinModel getModel() {
return skinModel;
}
protected SkinData withModel(SkinModel model) {
if(model != null && skinModel != model) {
if(inverseModel == null) {
return inverseModel = new SkinData() {
@Override
public ResourceLocation getLocation() {
return skinLocation;
}
@Override
public SkinModel getModel() {
return model;
}
};
}
return inverseModel;
}else {
return this;
}
}
protected void handleSkinResultPreset(int skinID) {
DefaultSkins skin = DefaultSkins.getSkinFromId(skinID);
skinLocation = skin.location;
skinModel = skin.model;
state |= ServerTextureCacheOld.STATE_S_LOADED;
}
protected void handleSkinResultCustomV4(byte[] customSkin, int modelID) {
handleSkinResultCustomV3(SkinPacketVersionCache.convertToV3Raw(customSkin), modelID);
}
protected void handleSkinResultCustomV3(byte[] customSkin, int modelID) {
if(modelID != 0xFF) {
skinModel = SkinModel.getSanitizedModelFromId(modelID);
}else if(skinModel == null) {
skinModel = SkinModel.STEVE;
}
skinTexture = new EaglerSkinTexture(customSkin, skinModel.width, skinModel.height);
state |= ServerTextureCacheOld.STATE_S_COMPLETE;
}
protected void loadSkin(TextureManager textureManager) {
skinLocation = new ResourceLocation("eagler:multiplayer/tex_" + ServerTextureCacheOld.texId++);
textureManager.loadTexture(skinLocation, skinTexture);
state |= ServerTextureCacheOld.STATE_S_LOADED;
}
protected void release(TextureManager textureManager) {
if(skinTexture != null && (state & ServerTextureCacheOld.STATE_S_LOADED) != 0) {
textureManager.deleteTexture(skinLocation);
}
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.skin_cache;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.DefaultCapes;
import net.lax1dude.eaglercraft.v1_8.profile.DefaultSkins;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerSkinTexture;
import net.lax1dude.eaglercraft.v1_8.profile.SkinConverter;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
class PlayerTextureEntry extends SkinData {
protected final EaglercraftUUID uuid;
protected int state;
protected long lastHit;
protected ResourceLocation skinLocation;
protected EaglerSkinTexture skinTexture;
protected SkinModel skinModel;
protected ResourceLocation capeLocation;
protected EaglerSkinTexture capeTexture;
public PlayerTextureEntry(EaglercraftUUID uuid) {
this.uuid = uuid;
}
@Override
public ResourceLocation getLocation() {
return skinLocation;
}
@Override
public SkinModel getModel() {
return skinModel;
}
protected void handleSkinResultPreset(int skinID) {
DefaultSkins skin = DefaultSkins.getSkinFromId(skinID);
skinLocation = skin.location;
skinModel = skin.model;
state |= ServerTextureCache.STATE_S_LOADED;
}
protected void handleSkinResultCustomV4(byte[] customSkin, int modelID) {
handleSkinResultCustomV3(SkinPacketVersionCache.convertToV3Raw(customSkin), modelID);
}
protected void handleSkinResultCustomV3(byte[] customSkin, int modelID) {
skinModel = SkinModel.getSanitizedModelFromId(modelID);
skinTexture = new EaglerSkinTexture(customSkin, skinModel.width, skinModel.height);
state |= ServerTextureCache.STATE_S_COMPLETE;
}
protected void loadSkin(TextureManager textureManager) {
skinLocation = new ResourceLocation("eagler:multiplayer/tex_" + ServerTextureCache.texId++);
textureManager.loadTexture(skinLocation, skinTexture);
state |= ServerTextureCache.STATE_S_LOADED;
}
protected void handleCapeResultPreset(int presetCape) {
DefaultCapes cape = DefaultCapes.getCapeFromId(presetCape);
capeLocation = cape.location;
state |= ServerTextureCache.STATE_C_LOADED;
}
protected void handleCapeResultCustom(byte[] customCape) {
byte[] pixels32x32 = new byte[4096];
SkinConverter.convertCape23x17RGBto32x32RGBA(customCape, pixels32x32);
capeTexture = new EaglerSkinTexture(pixels32x32, 32, 32);
state |= ServerTextureCache.STATE_C_COMPLETE;
}
protected void loadCape(TextureManager textureManager) {
capeLocation = new ResourceLocation("eagler:multiplayer/tex_" + ServerTextureCache.texId++);
textureManager.loadTexture(capeLocation, capeTexture);
state |= ServerTextureCache.STATE_C_LOADED;
}
protected void release(TextureManager textureManager) {
if(skinTexture != null && (state & ServerTextureCache.STATE_S_LOADED) != 0) {
textureManager.deleteTexture(skinLocation);
}
if(capeTexture != null && (state & ServerTextureCache.STATE_C_LOADED) != 0) {
textureManager.deleteTexture(capeLocation);
}
}
protected void drop(TextureManager textureManager, boolean skin, boolean cape) {
if(skin) {
if(skinTexture != null && (state & ServerTextureCache.STATE_S_LOADED) != 0) {
textureManager.deleteTexture(skinLocation);
}
skinTexture = null;
skinLocation = null;
state &= ~(ServerTextureCache.STATE_S_PENDING | ServerTextureCache.STATE_S_LOADED
| ServerTextureCache.STATE_S_COMPLETE);
}
if(cape) {
if(capeTexture != null && (state & ServerTextureCache.STATE_C_LOADED) != 0) {
textureManager.deleteTexture(capeLocation);
}
capeTexture = null;
capeLocation = null;
state &= ~(ServerTextureCache.STATE_C_PENDING | ServerTextureCache.STATE_C_LOADED
| ServerTextureCache.STATE_C_COMPLETE);
}
}
}

View File

@ -0,0 +1,364 @@
/*
* 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.skin_cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.carrotsearch.hppc.ObjectLongHashMap;
import com.carrotsearch.hppc.ObjectLongMap;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.mojang.authlib.GameProfile;
import net.lax1dude.eaglercraft.v1_8.mojang.authlib.TexturesProperty;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.client.StateFlags;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
public abstract class ServerTextureCache {
static final int STATE_S_PENDING = 1;
static final int STATE_C_PENDING = 2;
static final int STATE_SC_PENDING = STATE_S_PENDING | STATE_C_PENDING;
static final int STATE_S_COMPLETE = 4;
static final int STATE_C_COMPLETE = 8;
static final int STATE_S_LOADED = 16;
static final int STATE_C_LOADED = 32;
static int texId = 0;
protected final int protocolVers;
protected final NetHandlerPlayClient netHandler;
protected final TextureManager textureManager;
protected final EaglercraftUUID self;
private EaglercraftUUID mruPlayerKey = null;
private PlayerTextureEntry mruPlayerTexture = null;
private final Map<EaglercraftUUID, PlayerTextureEntry> playerTextures = new HashMap<>(256);
private EaglercraftUUID mruForeignKey = null;
private ForeignTextureEntry mruForeignTexture = null;
private final Map<EaglercraftUUID, ForeignTextureEntry> foreignTextures = new HashMap<>(256);
private final Set<PlayerTextureEntry> pendingPlayerToLookup = new HashSet<>(32);
private final Set<ForeignTextureEntry> pendingForeignToLookup = new HashSet<>(32);
private final ObjectLongMap<EaglercraftUUID> evictedPlayers = new ObjectLongHashMap<>(32);
private int nextFlush = 200;
private int nextEvictFlush = 20;
private int flush = 0;
public static ServerTextureCache create(NetHandlerPlayClient netHandler, TextureManager textureManager) {
if (netHandler.getEaglerMessageProtocol().ver >= 5) {
return new ServerTextureCacheV5(netHandler, textureManager);
} else {
return new ServerTextureCacheOld(netHandler, textureManager);
}
}
public ServerTextureCache(NetHandlerPlayClient netHandler, TextureManager textureManager) {
this.protocolVers = netHandler.getEaglerMessageProtocol().ver;
this.netHandler = netHandler;
this.textureManager = textureManager;
this.self = netHandler.getGameProfile().getId();
}
private PlayerTextureEntry loadPlayer(EaglercraftUUID uuid) {
if (mruPlayerKey != null && uuid.equals(mruPlayerKey)) {
return mruPlayerTexture;
}
PlayerTextureEntry ret = playerTextures.get(uuid);
if (ret == null) {
playerTextures.put(uuid, ret = new PlayerTextureEntry(uuid));
}
ret.lastHit = EagRuntime.steadyTimeMillis();
mruPlayerKey = uuid;
mruPlayerTexture = ret;
return ret;
}
private ForeignTextureEntry loadForeign(EaglercraftUUID uuid, String url, SkinModel model) {
if (mruForeignKey != null && uuid.equals(mruForeignKey)) {
return mruForeignTexture;
}
ForeignTextureEntry ret = foreignTextures.get(uuid);
if (ret == null) {
foreignTextures.put(uuid, ret = new ForeignTextureEntry(uuid, url, model));
}
ret.lastHit = EagRuntime.steadyTimeMillis();
mruForeignKey = uuid;
mruForeignTexture = ret;
return ret;
}
public SkinData getPlayerSkin(GameProfile profile) {
if (self.equals(profile.getId())) {
return SkinData.defaultSkinSelf;
}
TexturesProperty prop = profile.getTextures();
if (prop.eaglerPlayer != (byte) 0) {
return getPlayerSkinImpl(profile, prop);
} else {
SkinData ret = getForeignSkinImpl(profile, prop);
if (ret != null) {
return ret;
} else if (StateFlags.eaglerPlayerFlag) {
return SkinData.getDefaultSkin(profile.getId());
} else {
return getPlayerSkinImpl(profile, prop);
}
}
}
private SkinData getPlayerSkinImpl(GameProfile profile, TexturesProperty prop) {
EaglercraftUUID uuid = profile.getId();
if (checkEvicted(uuid)) {
return SkinData.getDefaultSkin(uuid);
}
PlayerTextureEntry etr = loadPlayer(uuid);
int state = etr.state;
if ((state & STATE_S_LOADED) != 0) {
return etr;
} else if ((state & STATE_S_COMPLETE) != 0) {
etr.loadSkin(textureManager);
return etr;
} else {
if ((state & STATE_S_PENDING) == 0) {
etr.state = (state | STATE_S_PENDING);
pendingPlayerToLookup.add(etr);
}
return SkinData.getDefaultSkin(profile.getId());
}
}
private SkinData getForeignSkinImpl(GameProfile profile, TexturesProperty prop) {
if(StateFlags.disableSkinURLLookup) {
return null;
}
String url = prop.skin;
if (url != null) {
ForeignTextureEntry etr = loadForeign(prop.loadSkinTextureUUID(), url, prop.model);
int state = etr.state;
if ((state & STATE_S_LOADED) != 0) {
return etr.withModel(prop.model);
} else if ((state & STATE_S_COMPLETE) != 0) {
etr.loadSkin(textureManager);
return etr.withModel(prop.model);
} else {
if ((state & STATE_S_PENDING) == 0) {
etr.state = (state | STATE_S_PENDING);
pendingForeignToLookup.add(etr);
}
return SkinData.getDefaultSkin(prop.model);
}
}
return null;
}
public ResourceLocation getPlayerCape(GameProfile profile) {
EaglercraftUUID uuid = profile.getId();
if (uuid.equals(self)) {
return EaglerProfile.getActiveCapeResourceLocation();
}
if (!StateFlags.eaglerPlayerFlag || profile.getTextures().eaglerPlayer != (byte) 0) {
if (checkEvicted(uuid)) {
return null;
}
PlayerTextureEntry etr = loadPlayer(uuid);
int state = etr.state;
if ((state & STATE_C_LOADED) != 0) {
return etr.capeLocation;
} else if ((state & STATE_C_COMPLETE) != 0) {
etr.loadCape(textureManager);
return etr.capeLocation;
} else {
if ((state & STATE_C_PENDING) == 0) {
etr.state = (state | STATE_C_PENDING);
pendingPlayerToLookup.add(etr);
}
return null;
}
}
// Loading capes by URL is not currently supported
// (Should only affect NPCs on a working setup)
return null;
}
public void dropPlayer(EaglercraftUUID playerUUID, boolean skin, boolean cape) {
if (skin || cape) {
PlayerTextureEntry etr = playerTextures.get(playerUUID);
if (etr != null) {
etr.drop(textureManager, skin, cape);
_dropPlayer(playerUUID, skin, cape);
}
}
}
protected abstract void _dropPlayer(EaglercraftUUID playerUUID, boolean skin, boolean cape);
public void evictPlayer(EaglercraftUUID playerUUID) {
PlayerTextureEntry etr = playerTextures.remove(playerUUID);
if (etr != null) {
etr.release(textureManager);
}
if (mruPlayerKey != null && playerUUID.equals(mruPlayerKey)) {
mruPlayerKey = null;
mruPlayerTexture = null;
}
evictedPlayers.put(playerUUID, EagRuntime.steadyTimeMillis() + 2000l);
}
private boolean checkEvicted(EaglercraftUUID playerUUID) {
if (!evictedPlayers.isEmpty()) {
long l = evictedPlayers.getOrDefault(playerUUID, -1l);
return l != -1l && EagRuntime.steadyTimeMillis() < l;
}
return false;
}
public void runTick() {
if (!pendingPlayerToLookup.isEmpty()) {
for (PlayerTextureEntry etr : pendingPlayerToLookup) {
int state = etr.state;
if (protocolVers >= 5 && (state & STATE_SC_PENDING) == STATE_SC_PENDING) {
sendTexturesRequest(etr);
} else {
if ((state & STATE_S_PENDING) != 0) {
sendSkinRequest(etr);
}
if ((state & STATE_C_PENDING) != 0) {
sendCapeRequest(etr);
}
}
}
pendingPlayerToLookup.clear();
}
if (!pendingForeignToLookup.isEmpty()) {
for (ForeignTextureEntry etr : pendingForeignToLookup) {
if ((etr.state & STATE_S_PENDING) != 0) {
sendSkinRequest(etr);
}
}
pendingForeignToLookup.clear();
}
if (--nextEvictFlush <= 0) {
nextEvictFlush = 20;
long now = EagRuntime.steadyTimeMillis();
evictedPlayers.removeAll((obj, millis) -> {
return now > millis;
});
}
if (--nextFlush <= 0) {
nextFlush = 200;
long now = EagRuntime.steadyTimeMillis();
lookupFlush(now);
if ((++flush & 3) == 0) {
if (!playerTextures.isEmpty()) {
Iterator<PlayerTextureEntry> itr1 = playerTextures.values().iterator();
while (itr1.hasNext()) {
PlayerTextureEntry etr = itr1.next();
if (now - etr.lastHit > (900l * 1000l)) {
itr1.remove();
etr.release(textureManager);
}
}
mruPlayerKey = null;
mruPlayerTexture = null;
}
if (!foreignTextures.isEmpty()) {
Iterator<ForeignTextureEntry> itr2 = foreignTextures.values().iterator();
while (itr2.hasNext()) {
ForeignTextureEntry etr = itr2.next();
if (now - etr.lastHit > (900l * 1000l)) {
itr2.remove();
etr.release(textureManager);
}
}
mruForeignKey = null;
mruForeignTexture = null;
}
}
}
}
public void destroy() {
if (!playerTextures.isEmpty()) {
for (PlayerTextureEntry etr : playerTextures.values()) {
etr.release(textureManager);
}
playerTextures.clear();
mruPlayerKey = null;
mruPlayerTexture = null;
}
if (!foreignTextures.isEmpty()) {
for (ForeignTextureEntry etr : foreignTextures.values()) {
etr.release(textureManager);
}
foreignTextures.clear();
mruForeignKey = null;
mruForeignTexture = null;
}
pendingPlayerToLookup.clear();
pendingForeignToLookup.clear();
evictedPlayers.clear();
}
protected abstract void lookupFlush(long now);
protected abstract void sendTexturesRequest(PlayerTextureEntry etr);
protected abstract void sendSkinRequest(PlayerTextureEntry etr);
protected abstract void sendSkinRequest(ForeignTextureEntry etr);
protected abstract void sendCapeRequest(PlayerTextureEntry etr);
public abstract void handlePacket(SPacketOtherSkinPresetEAG packet);
public abstract void handlePacket(SPacketOtherSkinPresetV5EAG packet);
public abstract void handlePacket(SPacketOtherSkinCustomV3EAG packet);
public abstract void handlePacket(SPacketOtherSkinCustomV4EAG packet);
public abstract void handlePacket(SPacketOtherSkinCustomV5EAG packet);
public abstract void handlePacket(SPacketOtherCapePresetEAG packet);
public abstract void handlePacket(SPacketOtherCapePresetV5EAG packet);
public abstract void handlePacket(SPacketOtherCapeCustomEAG packet);
public abstract void handlePacket(SPacketOtherCapeCustomV5EAG packet);
public abstract void handlePacket(SPacketOtherTexturesV5EAG packet);
}

View File

@ -0,0 +1,233 @@
/*
* 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.skin_cache;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.texture.TextureManager;
public class ServerTextureCacheOld extends ServerTextureCache {
private static abstract class PendingLookup {
protected final long expiresAt = EagRuntime.steadyTimeMillis() + (30l * 1000l);
protected abstract void handleResult(GameMessagePacket packet);
protected abstract void handleTimeout();
}
private final Map<EaglercraftUUID, PendingLookup> pendingSkinLookup = new HashMap<>(64);
private final Map<EaglercraftUUID, PendingLookup> pendingCapeLookup = new HashMap<>(64);
public ServerTextureCacheOld(NetHandlerPlayClient netHandler, TextureManager textureManager) {
super(netHandler, textureManager);
if(protocolVers > 4) {
throw new IllegalStateException();
}
}
@Override
protected void _dropPlayer(EaglercraftUUID playerUUID, boolean skin, boolean cape) {
if(skin) {
pendingSkinLookup.remove(playerUUID);
}
if(cape) {
pendingCapeLookup.remove(playerUUID);
}
}
@Override
protected void sendSkinRequest(PlayerTextureEntry etr) {
pendingSkinLookup.put(etr.uuid, new PendingLookup() {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_S_PENDING;
if(packet instanceof SPacketOtherSkinPresetEAG) {
SPacketOtherSkinPresetEAG pkt = (SPacketOtherSkinPresetEAG) packet;
etr.handleSkinResultPreset(pkt.presetSkin);
}else if(packet instanceof SPacketOtherSkinCustomV4EAG) {
SPacketOtherSkinCustomV4EAG pkt = (SPacketOtherSkinCustomV4EAG) packet;
etr.handleSkinResultCustomV4(pkt.customSkin, pkt.modelID);
}else if(packet instanceof SPacketOtherSkinCustomV3EAG) {
SPacketOtherSkinCustomV3EAG pkt = (SPacketOtherSkinCustomV3EAG) packet;
etr.handleSkinResultCustomV3(pkt.customSkin, pkt.modelID);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_S_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetOtherSkinEAG(etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits()));
}
@Override
protected void sendCapeRequest(PlayerTextureEntry etr) {
pendingCapeLookup.put(etr.uuid, new PendingLookup() {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_C_PENDING;
if(packet instanceof SPacketOtherCapePresetEAG) {
SPacketOtherCapePresetEAG pkt = (SPacketOtherCapePresetEAG) packet;
etr.handleCapeResultPreset(pkt.presetCape);
}else if(packet instanceof SPacketOtherCapeCustomEAG) {
SPacketOtherCapeCustomEAG pkt = (SPacketOtherCapeCustomEAG) packet;
etr.handleCapeResultCustom(pkt.customCape);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_C_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetOtherCapeEAG(etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits()));
}
@Override
protected void sendSkinRequest(ForeignTextureEntry etr) {
pendingSkinLookup.put(etr.uuid, new PendingLookup() {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_S_PENDING;
if(packet instanceof SPacketOtherSkinPresetEAG) {
SPacketOtherSkinPresetEAG pkt = (SPacketOtherSkinPresetEAG) packet;
etr.handleSkinResultPreset(pkt.presetSkin);
}else if(packet instanceof SPacketOtherSkinCustomV4EAG) {
SPacketOtherSkinCustomV4EAG pkt = (SPacketOtherSkinCustomV4EAG) packet;
etr.handleSkinResultCustomV4(pkt.customSkin, pkt.modelID);
}else if(packet instanceof SPacketOtherSkinCustomV3EAG) {
SPacketOtherSkinCustomV3EAG pkt = (SPacketOtherSkinCustomV3EAG) packet;
etr.handleSkinResultCustomV3(pkt.customSkin, pkt.modelID);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_S_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetSkinByURLEAG(etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits(), etr.url));
}
@Override
protected void sendTexturesRequest(PlayerTextureEntry etr) {
throw new IllegalStateException();
}
@Override
protected void lookupFlush(long now) {
lookupFlush(pendingSkinLookup, now);
lookupFlush(pendingCapeLookup, now);
}
private void lookupFlush(Map<?, PendingLookup> pending, long now) {
if(!pending.isEmpty()) {
Iterator<PendingLookup> itr1 = pending.values().iterator();
while(itr1.hasNext()) {
PendingLookup etr = itr1.next();
if(now > etr.expiresAt) {
itr1.remove();
etr.handleTimeout();
}
}
}
}
@Override
public void handlePacket(SPacketOtherSkinPresetEAG packet) {
PendingLookup lookup = pendingSkinLookup.remove(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherSkinPresetV5EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherSkinCustomV3EAG packet) {
PendingLookup lookup = pendingSkinLookup.remove(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherSkinCustomV4EAG packet) {
PendingLookup lookup = pendingSkinLookup.remove(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherSkinCustomV5EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherCapePresetEAG packet) {
PendingLookup lookup = pendingCapeLookup.remove(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherCapePresetV5EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherCapeCustomEAG packet) {
PendingLookup lookup = pendingCapeLookup.remove(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherCapeCustomV5EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherTexturesV5EAG packet) {
throw new IllegalStateException();
}
}

View File

@ -0,0 +1,270 @@
/*
* 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.skin_cache;
import com.carrotsearch.hppc.IntObjectHashMap;
import com.carrotsearch.hppc.IntObjectMap;
import com.carrotsearch.hppc.predicates.IntObjectPredicate;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.texture.TextureManager;
public class ServerTextureCacheV5 extends ServerTextureCache {
private static abstract class PendingLookup {
protected final EaglercraftUUID uuid;
protected final long expiresAt = EagRuntime.steadyTimeMillis() + (30l * 1000l);
protected PendingLookup(EaglercraftUUID uuid) {
this.uuid = uuid;
}
protected abstract void handleResult(GameMessagePacket packet);
protected abstract void handleTimeout();
}
private final IntObjectMap<PendingLookup> pendingSkinLookup = new IntObjectHashMap<>(64);
private final IntObjectMap<PendingLookup> pendingCapeLookup = new IntObjectHashMap<>(64);
private final IntObjectMap<PendingLookup> pendingTextureLookup = new IntObjectHashMap<>(64);
private int lookupIdA = 0;
private int lookupIdB = 0;
private int lookupIdC = 0;
public ServerTextureCacheV5(NetHandlerPlayClient netHandler, TextureManager textureManager) {
super(netHandler, textureManager);
}
private int nextLookupIdA() {
return lookupIdA = (lookupIdA + 1) & 0x3FFF;
}
private int nextLookupIdB() {
return lookupIdB = (lookupIdB + 1) & 0x3FFF;
}
private int nextLookupIdC() {
return lookupIdC = (lookupIdC + 1) & 0x3FFF;
}
@Override
protected void sendSkinRequest(PlayerTextureEntry etr) {
int lookupId = nextLookupIdA();
pendingSkinLookup.put(lookupId, new PendingLookup(etr.uuid) {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_S_PENDING;
if(packet instanceof SPacketOtherSkinPresetV5EAG) {
SPacketOtherSkinPresetV5EAG pkt = (SPacketOtherSkinPresetV5EAG) packet;
etr.handleSkinResultPreset(pkt.presetSkin);
}else if(packet instanceof SPacketOtherSkinCustomV5EAG) {
SPacketOtherSkinCustomV5EAG pkt = (SPacketOtherSkinCustomV5EAG) packet;
etr.handleSkinResultCustomV4(pkt.customSkin, pkt.modelID);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_S_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetOtherSkinV5EAG(lookupId, etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits()));
}
@Override
protected void sendCapeRequest(PlayerTextureEntry etr) {
int lookupId = nextLookupIdB();
pendingCapeLookup.put(lookupId, new PendingLookup(etr.uuid) {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_C_PENDING;
if(packet instanceof SPacketOtherCapePresetV5EAG) {
SPacketOtherCapePresetV5EAG pkt = (SPacketOtherCapePresetV5EAG) packet;
etr.handleCapeResultPreset(pkt.presetCape);
}else if(packet instanceof SPacketOtherCapeCustomV5EAG) {
SPacketOtherCapeCustomV5EAG pkt = (SPacketOtherCapeCustomV5EAG) packet;
etr.handleCapeResultCustom(pkt.customCape);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_C_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetOtherCapeV5EAG(lookupId, etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits()));
}
@Override
protected void sendSkinRequest(ForeignTextureEntry etr) {
int lookupId = nextLookupIdA();
pendingSkinLookup.put(lookupId, new PendingLookup(null) {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_S_PENDING;
if(packet instanceof SPacketOtherSkinPresetV5EAG) {
SPacketOtherSkinPresetV5EAG pkt = (SPacketOtherSkinPresetV5EAG) packet;
etr.handleSkinResultPreset(pkt.presetSkin);
}else if(packet instanceof SPacketOtherSkinCustomV5EAG) {
SPacketOtherSkinCustomV5EAG pkt = (SPacketOtherSkinCustomV5EAG) packet;
etr.handleSkinResultCustomV4(pkt.customSkin, pkt.modelID);
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_S_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetSkinByURLV5EAG(lookupId, etr.url));
}
@Override
protected void sendTexturesRequest(PlayerTextureEntry etr) {
int lookupId = nextLookupIdC();
pendingTextureLookup.put(lookupId, new PendingLookup(etr.uuid) {
@Override
protected void handleResult(GameMessagePacket packet) {
etr.state &= ~STATE_SC_PENDING;
if(packet instanceof SPacketOtherTexturesV5EAG) {
SPacketOtherTexturesV5EAG pkt = (SPacketOtherTexturesV5EAG) packet;
if(pkt.skinID >= 0) {
etr.handleSkinResultPreset(pkt.skinID);
}else {
etr.handleSkinResultCustomV4(pkt.customSkin, -pkt.skinID - 1);
}
if(pkt.capeID >= 0) {
etr.handleCapeResultPreset(pkt.capeID);
}else {
etr.handleCapeResultCustom(pkt.customCape);
}
}else {
throw new IllegalStateException();
}
}
@Override
protected void handleTimeout() {
etr.state &= ~STATE_SC_PENDING;
}
});
netHandler.sendEaglerMessage(new CPacketGetOtherTexturesV5EAG(lookupId, etr.uuid.getMostSignificantBits(),
etr.uuid.getLeastSignificantBits()));
}
@Override
protected void _dropPlayer(EaglercraftUUID playerUUID, boolean skin, boolean cape) {
IntObjectPredicate<PendingLookup> pred = (i, o) -> {
return o.uuid != null && playerUUID.equals(o.uuid);
};
if(skin) {
pendingSkinLookup.removeAll(pred);
}
if(cape) {
pendingCapeLookup.removeAll(pred);
}
pendingTextureLookup.removeAll(pred);
}
@Override
protected void lookupFlush(long now) {
IntObjectPredicate<PendingLookup> pred = (i, o) -> {
return now > o.expiresAt;
};
pendingSkinLookup.removeAll(pred);
pendingCapeLookup.removeAll(pred);
pendingTextureLookup.removeAll(pred);
}
@Override
public void handlePacket(SPacketOtherSkinPresetEAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherSkinPresetV5EAG packet) {
PendingLookup lookup = pendingSkinLookup.remove(packet.requestId);
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherSkinCustomV3EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherSkinCustomV4EAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherSkinCustomV5EAG packet) {
PendingLookup lookup = pendingSkinLookup.remove(packet.requestId);
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherCapePresetEAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherCapePresetV5EAG packet) {
PendingLookup lookup = pendingCapeLookup.remove(packet.requestId);
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherCapeCustomEAG packet) {
throw new IllegalStateException();
}
@Override
public void handlePacket(SPacketOtherCapeCustomV5EAG packet) {
PendingLookup lookup = pendingCapeLookup.remove(packet.requestId);
if(lookup != null) {
lookup.handleResult(packet);
}
}
@Override
public void handlePacket(SPacketOtherTexturesV5EAG packet) {
PendingLookup lookup = pendingTextureLookup.remove(packet.requestId);
if(lookup != null) {
lookup.handleResult(packet);
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.skin_cache;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.DefaultSkins;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.minecraft.util.ResourceLocation;
public abstract class SkinData {
public abstract ResourceLocation getLocation();
public abstract SkinModel getModel();
static SkinData getDefaultSkin(EaglercraftUUID uuid) {
return (uuid != null && (uuid.hashCode() & 1) != 0) ? defaultSkinDataAlex : defaultSkinDataSteve;
}
static SkinData getDefaultSkin(SkinModel model) {
return (model == SkinModel.ALEX) ? defaultSkinDataAlex : defaultSkinDataSteve;
}
static final SkinData defaultSkinDataSteve = new SkinData() {
@Override
public ResourceLocation getLocation() {
return DefaultSkins.DEFAULT_STEVE.location;
}
@Override
public SkinModel getModel() {
return SkinModel.STEVE;
}
};
static final SkinData defaultSkinDataAlex = new SkinData() {
@Override
public ResourceLocation getLocation() {
return DefaultSkins.DEFAULT_ALEX.location;
}
@Override
public SkinModel getModel() {
return SkinModel.ALEX;
}
};
static final SkinData defaultSkinSelf = new SkinData() {
@Override
public ResourceLocation getLocation() {
return EaglerProfile.getActiveSkinResourceLocation();
}
@Override
public SkinModel getModel() {
return EaglerProfile.getActiveSkinModel();
}
};
}

View File

@ -1,507 +0,0 @@
/*
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.ClientUUIDLoadingCache;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EagUtils;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.EaglerOutputStream;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.PauseMenuCustomizeState;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA256Digest;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.GuiAuthenticationScreen;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.GuiConnecting;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
public class ConnectionHandshake {
private static final long baseTimeout = 10000l;
private static final int protocolV2 = 2;
private static final int protocolV3 = 3;
private static final int protocolV4 = 4;
private static final Logger logger = LogManager.getLogger();
public static String pluginVersion = null;
public static String pluginBrand = null;
public static int protocolVersion = -1;
public static byte[] getSPHandshakeProtocolData() {
try {
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeShort(3); // supported eagler protocols count
d.writeShort(protocolV2); // client supports v2
d.writeShort(protocolV3); // client supports v3
d.writeShort(protocolV4); // client supports v4
return bao.toByteArray();
}catch(IOException ex) {
throw new RuntimeException(ex);
}
}
public static boolean attemptHandshake(Minecraft mc, IWebSocketClient client, GuiConnecting connecting,
GuiScreen ret, String password, boolean allowPlaintext, boolean enableCookies, byte[] cookieData) {
try {
EaglerProfile.clearServerSkinOverride();
PauseMenuCustomizeState.reset();
ClientUUIDLoadingCache.resetFlags();
pluginVersion = null;
pluginBrand = null;
protocolVersion = -1;
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_VERSION);
d.writeByte(2); // legacy protocol version
d.write(getSPHandshakeProtocolData()); // write supported eagler protocol versions
d.writeShort(1); // supported game protocols count
d.writeShort(47); // client supports 1.8 protocol
String clientBrand = EaglercraftVersion.projectForkName;
d.writeByte(clientBrand.length());
d.writeBytes(clientBrand);
String clientVers = EaglercraftVersion.projectOriginVersion;
d.writeByte(clientVers.length());
d.writeBytes(clientVers);
d.writeBoolean(password != null);
String username = mc.getSession().getProfile().getName();
d.writeByte(username.length());
d.writeBytes(username);
client.send(bao.toByteArray());
byte[] read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for server protocol response!");
return false;
}
DataInputStream di = new DataInputStream(new EaglerInputStream(read));
int type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH) {
StringBuilder protocols = new StringBuilder();
int c = di.readShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
protocols.append(", ");
}
protocols.append("v").append(di.readShort());
}
StringBuilder games = new StringBuilder();
c = di.readShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
games.append(", ");
}
games.append("mc").append(di.readShort());
}
logger.info("Incompatible client: v2/v3/v4 & mc47");
logger.info("Server supports: {}", protocols);
logger.info("Server supports: {}", games);
int msgLen = di.read();
byte[] dat = new byte[msgLen];
di.read(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText(msg)));
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_VERSION) {
protocolVersion = di.readShort();
if(protocolVersion != protocolV2 && protocolVersion != protocolV3 && protocolVersion != protocolV4) {
logger.info("Incompatible server version: {}", protocolVersion);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText(protocolVersion < protocolV2 ? "Outdated Server" : "Outdated Client")));
return false;
}
int gameVers = di.readShort();
if(gameVers != 47) {
logger.info("Incompatible minecraft protocol version: {}", gameVers);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText("This server does not support 1.8!")));
return false;
}
logger.info("Server protocol: {}", protocolVersion);
int msgLen = di.read();
byte[] dat = new byte[msgLen];
di.read(dat);
pluginBrand = ArrayUtils.asciiString(dat);
msgLen = di.read();
dat = new byte[msgLen];
di.read(dat);
pluginVersion = ArrayUtils.asciiString(dat);
logger.info("Server version: {}", pluginVersion);
logger.info("Server brand: {}", pluginBrand);
int authType = di.read();
int saltLength = (int)di.readShort() & 0xFFFF;
byte[] salt = new byte[saltLength];
di.read(salt);
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
d.writeByte(username.length());
d.writeBytes(username);
String requestedServer = "default";
d.writeByte(requestedServer.length());
d.writeBytes(requestedServer);
if(authType != 0 && password != null && password.length() > 0) {
if(authType == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
if(allowPlaintext) {
logger.warn("Server is using insecure plaintext authentication");
d.writeByte(password.length() << 1);
d.writeChars(password);
}else {
logger.error("Plaintext authentication was attempted but no user confirmation has been given to proceed");
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed",
new ChatComponentText(EnumChatFormatting.RED + "Plaintext authentication was attempted but no user confirmation has been given to proceed")));
return false;
}
}else if(authType == HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256) {
SHA256Digest digest = new SHA256Digest();
int passLen = password.length();
digest.update((byte)((passLen >>> 8) & 0xFF));
digest.update((byte)(passLen & 0xFF));
for(int i = 0; i < passLen; ++i) {
char codePoint = password.charAt(i);
digest.update((byte)((codePoint >>> 8) & 0xFF));
digest.update((byte)(codePoint & 0xFF));
}
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_SAVE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 0, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 32, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
d.writeByte(32);
d.write(hashed);
}else if(authType == HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
SHA256Digest digest = new SHA256Digest();
byte[] passwd = password.getBytes(StandardCharsets.UTF_8);
digest.update(passwd, 0, passwd.length);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
byte[] toHexAndSalt = new byte[64];
for(int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
digest.reset();
digest.update(toHexAndSalt, 0, 64);
digest.update(salt, 0, salt.length);
digest.doFinal(hashed, 0);
for(int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
d.writeByte(64);
d.write(toHexAndSalt);
}else {
logger.error("Unsupported authentication type: {}", authType);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed",
new ChatComponentText(EnumChatFormatting.RED + "Unsupported authentication type: " + authType + "\n\n" + EnumChatFormatting.GRAY + "(Use a newer version of the client)")));
return false;
}
}else {
d.writeByte(0);
}
if(protocolVersion >= protocolV4) {
d.writeBoolean(enableCookies);
if(enableCookies && cookieData != null) {
d.writeByte(cookieData.length);
d.write(cookieData);
}else {
d.writeByte(0);
}
}
client.send(bao.toByteArray());
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login negotiation response!");
return false;
}
di = new DataInputStream(new EaglerInputStream(read));
type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN) {
msgLen = di.read();
dat = new byte[msgLen];
di.read(dat);
String serverUsername = ArrayUtils.asciiString(dat);
Minecraft.getMinecraft().getSession().update(serverUsername, new EaglercraftUUID(di.readLong(), di.readLong()));
Map<String,byte[]> profileDataToSend = new HashMap<>();
if(protocolVersion >= 4) {
bao.reset();
d.writeLong(EaglercraftVersion.clientBrandUUID.msb);
d.writeLong(EaglercraftVersion.clientBrandUUID.lsb);
profileDataToSend.put("brand_uuid_v1", bao.toByteArray());
}
byte[] packetSkin = EaglerProfile.getSkinPacket(protocolVersion);
if(packetSkin.length > 0xFFFF) {
throw new IOException("Skin packet is too long: " + packetSkin.length);
}
profileDataToSend.put(protocolVersion >= 4 ? "skin_v2" : "skin_v1", packetSkin);
byte[] packetCape = EaglerProfile.getCapePacket();
if(packetCape.length > 0xFFFF) {
throw new IOException("Cape packet is too long: " + packetCape.length);
}
profileDataToSend.put("cape_v1", packetCape);
byte[] packetSignatureData = UpdateService.getClientSignatureData();
if(packetSignatureData != null) {
profileDataToSend.put("update_cert_v1", packetSignatureData);
}
if(protocolVersion >= 4) {
List<Entry<String,byte[]>> toSend = new ArrayList<>(profileDataToSend.entrySet());
while(!toSend.isEmpty()) {
int sendLen = 2;
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
d.writeByte(0); // will be replaced
int packetCount = 0;
while(!toSend.isEmpty() && packetCount < 255) {
Entry<String,byte[]> etr = toSend.get(toSend.size() - 1);
int i = 3 + etr.getKey().length() + etr.getValue().length;
if(sendLen + i < 0xFF00) {
String profileDataType = etr.getKey();
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] data = etr.getValue();
d.writeShort(data.length);
d.write(data);
toSend.remove(toSend.size() - 1);
++packetCount;
}else {
break;
}
}
byte[] send = bao.toByteArray();
send[1] = (byte)packetCount;
client.send(send);
}
}else {
for(Entry<String,byte[]> etr : profileDataToSend.entrySet()) {
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
String profileDataType = etr.getKey();
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] data = etr.getValue();
d.writeShort(data.length);
d.write(data);
client.send(bao.toByteArray());
}
}
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_FINISH_LOGIN);
client.send(bao.toByteArray());
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login confirmation response!");
return false;
}
di = new DataInputStream(new EaglerInputStream(read));
type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN) {
return true;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN) {
if(protocolVersion == protocolV2) {
msgLen = di.read();
}else {
msgLen = di.readUnsignedShort();
}
dat = new byte[msgLen];
di.read(dat);
String errStr = new String(dat, StandardCharsets.UTF_8);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", IChatComponent.Serializer.jsonToComponent(errStr)));
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, true);
return false;
}else {
return false;
}
}catch(Throwable t) {
logger.error("Exception in handshake");
logger.error(t);
return false;
}
}
private static byte[] awaitNextPacket(IWebSocketClient client, long timeout) {
long millis = EagRuntime.steadyTimeMillis();
IWebSocketFrame b;
while((b = client.getNextBinaryFrame()) == null) {
if(client.getState().isClosed()) {
return null;
}
EagUtils.sleep(50);
if(EagRuntime.steadyTimeMillis() - millis > timeout) {
client.close();
return null;
}
}
return b.getByteArray();
}
private static void showError(Minecraft mc, IWebSocketClient client, GuiConnecting connecting, GuiScreen scr, DataInputStream err, boolean v2) throws IOException {
int errorCode = err.read();
int msgLen = v2 ? err.read() : err.readUnsignedShort();
// workaround for bug in EaglerXBungee 1.2.7 and below
if(msgLen == 0) {
if(v2) {
if(err.available() == 256) {
msgLen = 256;
}
}else {
if(err.available() == 65536) {
msgLen = 65536;
}
}
}
byte[] dat = new byte[msgLen];
err.read(dat);
String errStr = new String(dat, StandardCharsets.UTF_8);
logger.info("Server Error Code {}: {}", errorCode, errStr);
if(errorCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_BLOCKED) {
RateLimitTracker.registerBlock(client.getCurrentURI());
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(scr));
}else if(errorCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_LOCKED) {
RateLimitTracker.registerLockOut(client.getCurrentURI());
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(scr));
}else if(errorCode == HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE) {
mc.displayGuiScreen(new GuiDisconnected(scr, "connect.failed", IChatComponent.Serializer.jsonToComponent(errStr)));
}else if(connecting != null && errorCode == HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED) {
mc.displayGuiScreen(new GuiAuthenticationScreen(connecting, scr, errStr));
}else {
mc.displayGuiScreen(new GuiDisconnected(scr, "connect.failed", new ChatComponentText("Server Error Code " + errorCode + "\n" + errStr)));
}
}
public static GuiScreen displayAuthProtocolConfirm(int protocol, GuiScreen no, GuiScreen yes) {
if(protocol == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
return new GuiHandshakeApprove("plaintext", no, yes);
}else if(protocol != HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256 && protocol != HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
return new GuiHandshakeApprove("unsupportedAuth", no);
}else {
return null;
}
}
private static final byte[] HEX = new byte[] {
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* Copyright (c) 2022-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
@ -22,6 +22,8 @@ import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.ServerCapabilities;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.message.InjectedMessageController;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.INetHandler;
import net.minecraft.network.Packet;
@ -38,6 +40,9 @@ public abstract class EaglercraftNetworkManager {
protected String pluginBrand = null;
protected String pluginVersion = null;
protected InjectedMessageController injectedController = null;
protected ServerCapabilities serverCapabilities = null;
public static final Logger logger = LogManager.getLogger("NetworkManager");
@ -45,10 +50,17 @@ public abstract class EaglercraftNetworkManager {
this.address = address;
this.temporaryBuffer = new PacketBuffer(Unpooled.buffer(0x1FFFF));
}
public void setPluginInfo(String pluginBrand, String pluginVersion) {
public void setPluginInfo(String pluginBrand, String pluginVersion, ServerCapabilities serverCapabilities) {
this.pluginBrand = pluginBrand;
this.pluginVersion = pluginVersion;
this.serverCapabilities = serverCapabilities;
}
public void setLANInfo(int protocolVer) {
this.pluginBrand = "integrated";
this.pluginVersion = "v" + protocolVer;
this.serverCapabilities = ServerCapabilities.getLAN();
}
public String getPluginBrand() {
@ -59,6 +71,14 @@ public abstract class EaglercraftNetworkManager {
return pluginVersion;
}
public ServerCapabilities getServerCapabilities() {
return serverCapabilities;
}
public void setInjectedMessageController(InjectedMessageController controller) {
injectedController = controller;
}
public abstract void connect();
public abstract EnumEaglerConnectionState getConnectStatus();
@ -109,5 +129,7 @@ public abstract class EaglercraftNetworkManager {
}
}
}
public abstract void injectRawFrame(byte[] data);
}

View File

@ -104,4 +104,14 @@ public class GuiHandshakeApprove extends GuiScreen {
}
}
public static GuiScreen displayAuthProtocolConfirm(int protocol, GuiScreen no, GuiScreen yes) {
if(protocol == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
return new GuiHandshakeApprove("plaintext", no, yes);
}else if(protocol != HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256 && protocol != HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
return new GuiHandshakeApprove("unsupportedAuth", no);
}else {
return null;
}
}
}

View File

@ -29,12 +29,15 @@ public class HandshakePacketTypes {
public static final int PROTOCOL_CLIENT_PROFILE_DATA = 0x07;
public static final int PROTOCOL_CLIENT_FINISH_LOGIN = 0x08;
public static final int PROTOCOL_SERVER_FINISH_LOGIN = 0x09;
public static final int PROTOCOL_SERVER_REDIRECT_TO = 0x0A;
public static final int PROTOCOL_SERVER_ERROR = 0xFF;
public static final int STATE_OPENED = 0x00;
public static final int STATE_CLIENT_VERSION = 0x01;
public static final int STATE_CLIENT_LOGIN = 0x02;
public static final int STATE_CLIENT_COMPLETE = 0x03;
public static final int STATE_NEW = 0x00;
public static final int STATE_OPENED = 0x01;
public static final int STATE_CLIENT_VERSION = 0x02;
public static final int STATE_CLIENT_LOGIN = 0x03;
public static final int STATE_CLIENT_COMPLETE = 0x04;
public static final int STATE_FINISHED = 0x05;
public static final int SERVER_ERROR_UNKNOWN_PACKET = 0x01;
public static final int SERVER_ERROR_INVALID_PACKET = 0x02;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* Copyright (c) 2022-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
@ -71,6 +71,11 @@ public class WebSocketNetworkManager extends EaglercraftNetworkManager {
++debugPacketCounter;
try {
byte[] asByteArray = next.getByteArray();
if(injectedController != null && injectedController.handlePacket(asByteArray, 0)) {
continue;
}
ByteBuf nettyBuffer = Unpooled.buffer(asByteArray, asByteArray.length);
nettyBuffer.writerIndex(asByteArray.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
@ -150,4 +155,13 @@ public class WebSocketNetworkManager extends EaglercraftNetworkManager {
}
}
@Override
public void injectRawFrame(byte[] data) {
if(!isChannelOpen()) {
logger.error("Frame was injected on a closed connection");
return;
}
webSocketClient.send(data);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.minecraft.client.network.NetHandlerPlayClient;
public abstract class ClientMessageHandler implements GameMessageHandler {
protected final NetHandlerPlayClient netHandler;
public ClientMessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
}
public static ClientMessageHandler createClientHandler(int version, NetHandlerPlayClient netHandler) {
switch(version) {
case 3:
return new ClientV3MessageHandler(netHandler);
case 4:
return new ClientV4MessageHandler(netHandler);
case 5:
return new ClientV5MessageHandler(netHandler);
default:
throw new UnsupportedOperationException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-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
@ -20,20 +20,16 @@ import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV3MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public class ClientV3MessageHandler extends ClientMessageHandler {
public ClientV3MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
super(netHandler);
}
public void handleServer(SPacketEnableFNAWSkinsEAG packet) {
@ -44,35 +40,19 @@ public class ClientV3MessageHandler implements GameMessageHandler {
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
netHandler.getCapeCache().cacheCapeCustom(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.customCape);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
netHandler.getCapeCache().cacheCapePreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetCape);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinCustomV3EAG packet) {
EaglercraftUUID responseUUID = new EaglercraftUUID(packet.uuidMost, packet.uuidLeast);
SkinModel modelId;
if(packet.modelID == (byte)0xFF) {
modelId = this.netHandler.getSkinCache().getRequestedSkinType(responseUUID);
}else {
modelId = SkinModel.getModelFromId(packet.modelID & 0x7F);
if((packet.modelID & 0x80) != 0 && modelId.sanitize) {
modelId = SkinModel.STEVE;
}
}
if(modelId.highPoly != null) {
modelId = SkinModel.STEVE;
}
this.netHandler.getSkinCache().cacheSkinCustom(responseUUID, packet.customSkin, modelId);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
this.netHandler.getSkinCache().cacheSkinPreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetSkin);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketUpdateCertEAG packet) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-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,81 +16,37 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.ClientUUIDLoadingCache;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.PauseMenuCustomizeState;
import net.lax1dude.eaglercraft.v1_8.cookie.ServerCookieDataStore;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.skin_cache.ServerTextureCache;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.lax1dude.eaglercraft.v1_8.webview.ServerInfoCache;
import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV4MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public class ClientV4MessageHandler extends ClientV3MessageHandler {
public ClientV4MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
super(netHandler);
}
public void handleServer(SPacketEnableFNAWSkinsEAG packet) {
netHandler.currentFNAWSkinAllowedState = packet.enableSkins;
netHandler.currentFNAWSkinForcedState = packet.force;
Minecraft.getMinecraft().getRenderManager().setEnableFNAWSkins(netHandler.currentFNAWSkinForcedState
|| (netHandler.currentFNAWSkinAllowedState && Minecraft.getMinecraft().gameSettings.enableFNAWSkins));
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
netHandler.getCapeCache().cacheCapeCustom(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.customCape);
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
netHandler.getCapeCache().cacheCapePreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetCape);
public void handleServer(SPacketOtherSkinCustomV3EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinCustomV4EAG packet) {
EaglercraftUUID responseUUID = new EaglercraftUUID(packet.uuidMost, packet.uuidLeast);
SkinModel modelId;
if(packet.modelID == (byte)0xFF) {
modelId = this.netHandler.getSkinCache().getRequestedSkinType(responseUUID);
}else {
modelId = SkinModel.getModelFromId(packet.modelID & 0x7F);
if((packet.modelID & 0x80) != 0 && modelId.sanitize) {
modelId = SkinModel.STEVE;
}
}
if(modelId.highPoly != null) {
modelId = SkinModel.STEVE;
}
this.netHandler.getSkinCache().cacheSkinCustom(responseUUID, SkinPacketVersionCache.convertToV3Raw(packet.customSkin), modelId);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
this.netHandler.getSkinCache().cacheSkinPreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.presetSkin);
}
public void handleServer(SPacketUpdateCertEAG packet) {
if (EagRuntime.getConfiguration().allowUpdateSvc()) {
UpdateService.addCertificateToSet(packet.updateCert);
}
}
public void handleServer(SPacketVoiceSignalAllowedEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeAllowed(packet.allowed, packet.iceServers);
}
public void handleServer(SPacketVoiceSignalConnectV3EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketVoiceSignalConnectV4EAG packet) {
@ -105,34 +61,6 @@ public class ClientV4MessageHandler implements GameMessageHandler {
}
}
public void handleServer(SPacketVoiceSignalDescEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeDescription(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
new String(packet.desc, StandardCharsets.UTF_8));
}
}
public void handleServer(SPacketVoiceSignalDisconnectPeerEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeDisconnect(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
}
public void handleServer(SPacketVoiceSignalGlobalEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeGlobalNew(packet.users);
}
}
public void handleServer(SPacketVoiceSignalICEEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeICECandidate(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
new String(packet.ice, StandardCharsets.UTF_8));
}
}
public void handleServer(SPacketForceClientSkinPresetV4EAG packet) {
EaglerProfile.handleForceSkinPreset(packet.presetSkin);
}
@ -166,14 +94,10 @@ public class ClientV4MessageHandler implements GameMessageHandler {
public void handleServer(SPacketInvalidatePlayerCacheV4EAG packet) {
if(packet.players != null && packet.players.size() > 0) {
ServerTextureCache textureCache = this.netHandler.getTextureCache();
for(SPacketInvalidatePlayerCacheV4EAG.InvalidateRequest req : packet.players) {
EaglercraftUUID uuid = new EaglercraftUUID(req.uuidMost, req.uuidLeast);
if(req.invalidateSkin) {
this.netHandler.getSkinCache().handleInvalidate(uuid);
}
if(req.invalidateCape) {
this.netHandler.getCapeCache().handleInvalidate(uuid);
}
textureCache.dropPlayer(new EaglercraftUUID(req.uuidMost, req.uuidLeast),
req.invalidateSkin, req.invalidateCape);
}
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenPhishingWarning;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenRecieveServerInfo;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenRequestDisplay;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenServerInfo;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV5MessageHandler extends ClientV4MessageHandler {
public ClientV5MessageHandler(NetHandlerPlayClient netHandler) {
super(netHandler);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinCustomV4EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinPresetV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinCustomV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapePresetV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapeCustomV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherTexturesV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketClientStateFlagV5EAG packet) {
StateFlags.setFlag(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.state);
}
public void handleServer(SPacketDisplayWebViewURLV5EAG packet) {
if (netHandler.allowedDisplayWebview && !netHandler.allowedDisplayWebviewYes) {
return;
}
Minecraft mc = Minecraft.getMinecraft();
GuiScreen screen = GuiScreenServerInfo.createForDisplayRequest(mc.currentScreen, packet.flags,
packet.embedTitle, packet.embedURL);
if (!mc.gameSettings.hasHiddenPhishWarning && !GuiScreenPhishingWarning.hasShownMessage) {
screen = new GuiScreenPhishingWarning(screen);
}
if (!netHandler.allowedDisplayWebview) {
mc.displayGuiScreen(new GuiScreenRequestDisplay(screen, mc.currentScreen, netHandler));
} else {
mc.displayGuiScreen(screen);
}
}
public void handleServer(SPacketDisplayWebViewBlobV5EAG packet) {
if (netHandler.allowedDisplayWebview && !netHandler.allowedDisplayWebviewYes) {
return;
}
Minecraft mc = Minecraft.getMinecraft();
GuiScreen screen = new GuiScreenRecieveServerInfo(mc.currentScreen, packet.embedHash,
(parent, blob, permissionsOriginUUID) -> {
return GuiScreenServerInfo.createForDisplayRequest(parent, packet.flags, packet.embedTitle, blob,
permissionsOriginUUID);
});
if (!mc.gameSettings.hasHiddenPhishWarning && !GuiScreenPhishingWarning.hasShownMessage) {
screen = new GuiScreenPhishingWarning(screen);
}
if (!netHandler.allowedDisplayWebview) {
mc.displayGuiScreen(new GuiScreenRequestDisplay(screen, mc.currentScreen, netHandler));
} else {
mc.displayGuiScreen(screen);
}
}
}

View File

@ -1,207 +0,0 @@
/*
* 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.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol.ServerV3MessageHandler;
import net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol.ServerV4MessageHandler;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.PacketBuffer;
public class GameProtocolMessageController {
private static final Logger logger = LogManager.getLogger("GameProtocolMessageController");
public final GamePluginMessageProtocol protocol;
public final int sendDirection;
public final int receiveDirection;
private final PacketBufferInputWrapper inputStream = new PacketBufferInputWrapper(null);
private final PacketBufferOutputWrapper outputStream = new PacketBufferOutputWrapper(null);
private final GameMessageHandler handler;
private final IPluginMessageSendFunction sendFunction;
private final List<PacketBuffer> sendQueueV4;
private final boolean noDelay;
public GameProtocolMessageController(GamePluginMessageProtocol protocol, int sendDirection, GameMessageHandler handler,
IPluginMessageSendFunction sendCallback) {
this.protocol = protocol;
this.sendDirection = sendDirection;
this.receiveDirection = GamePluginMessageConstants.oppositeDirection(sendDirection);
this.handler = handler;
this.sendFunction = sendCallback;
this.noDelay = protocol.ver < 4 || EagRuntime.getConfiguration().isEaglerNoDelay();
this.sendQueueV4 = !noDelay ? new LinkedList<>() : null;
}
public boolean handlePacket(String channel, PacketBuffer data) throws IOException {
GameMessagePacket pkt;
if(protocol.ver >= 4 && data.readableBytes() > 0 && data.getByte(data.readerIndex()) == (byte) 0xFF
&& channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
data.readByte();
inputStream.buffer = data;
int count = inputStream.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = data.readVarIntFromBuffer();
k = data.readerIndex() + j;
if(j > data.readableBytes()) {
throw new IOException("Packet fragment is too long: " + j + " > " + data.readableBytes());
}
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null) {
try {
pkt.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", pkt.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
}else {
logger.warn("Could not read packet fragment {} of {}, unknown packet", count, i);
}
if(data.readerIndex() != k) {
logger.warn("Packet fragment {} was the wrong length: {} != {}",
(pkt != null ? pkt.getClass().getSimpleName() : "unknown"), j + data.readerIndex() - k, j);
data.readerIndex(k);
}
}
if(data.readableBytes() > 0) {
logger.warn("Leftover data after reading multi-packet! ({} bytes)", data.readableBytes());
}
inputStream.buffer = null;
return true;
}
inputStream.buffer = data;
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null && inputStream.available() > 0) {
logger.warn("Leftover data after reading packet {}! ({} bytes)", pkt.getClass().getSimpleName(), inputStream.available());
}
inputStream.buffer = null;
if(pkt != null) {
try {
pkt.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", pkt.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
return true;
}else {
return false;
}
}
public void sendPacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
String chan = protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && j + 1 != len) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
if(sendQueueV4 != null && chan.equals(GamePluginMessageConstants.V4_CHANNEL)) {
sendQueueV4.add(buf);
}else {
sendFunction.sendPluginMessage(chan, buf);
}
}
public void flush() {
if(sendQueueV4 != null) {
int queueLen = sendQueueV4.size();
PacketBuffer pkt;
if(queueLen == 0) {
return;
}else if(queueLen == 1) {
pkt = sendQueueV4.remove(0);
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, pkt);
}else {
int i, j, sendCount, totalLen, lastLen;
PacketBuffer sendBuffer;
while(sendQueueV4.size() > 0) {
sendCount = 0;
totalLen = 0;
Iterator<PacketBuffer> itr = sendQueueV4.iterator();
do {
i = itr.next().readableBytes();
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && itr.hasNext());
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
pkt = sendQueueV4.remove(0);
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, pkt);
continue;
}
sendBuffer = new PacketBuffer(Unpooled.buffer(1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)));
sendBuffer.writeByte(0xFF);
sendBuffer.writeVarIntToBuffer(sendCount);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
sendBuffer.writeVarIntToBuffer(pkt.readableBytes());
sendBuffer.writeBytes(pkt);
}
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, sendBuffer);
}
}
}
}
public static GameMessageHandler createClientHandler(int protocolVersion, NetHandlerPlayClient netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ClientV3MessageHandler(netHandler);
case 4:
return new ClientV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, NetHandlerPlayServer netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(netHandler);
case 4:
return new ServerV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
public class StateFlags {
public static final EaglercraftUUID EAGLER_PLAYER_FLAG_PRESENT = new EaglercraftUUID(0x55F63601694140D9L,
0xB77BCE7B99A62E52L);
public static final EaglercraftUUID LEGACY_EAGLER_PLAYER_FLAG_PRESENT = new EaglercraftUUID(0xEEEEA64771094C4EL,
0x86E55B81D17E67EBL);
public static final EaglercraftUUID DISABLE_SKIN_URL_LOOKUP = new EaglercraftUUID(0xC41D641BE2DA4094L,
0xB1B2DFF2E9D08180L);
public static boolean eaglerPlayerFlag = false;
public static boolean eaglerPlayerFlagSupervisor = false;
public static boolean disableSkinURLLookup = false;
public static void setFlag(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;
}
}
public static void reset() {
eaglerPlayerFlag = false;
eaglerPlayerFlagSupervisor = false;
disableSkinURLLookup = false;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2022-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.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA256Digest;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
public class AuthTypes {
public static byte[] applyEaglerSHA256(String password, byte[] salt) {
SHA256Digest digest = new SHA256Digest();
int passLen = password.length();
digest.update((byte)((passLen >>> 8) & 0xFF));
digest.update((byte)(passLen & 0xFF));
for (int i = 0; i < passLen; ++i) {
char codePoint = password.charAt(i);
digest.update((byte)((codePoint >>> 8) & 0xFF));
digest.update((byte)(codePoint & 0xFF));
}
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_SAVE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 0, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 32, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
return hashed;
}
public static byte[] applyAuthMeSHA256(String password, byte[] salt) {
SHA256Digest digest = new SHA256Digest();
byte[] passwd = password.getBytes(StandardCharsets.UTF_8);
digest.update(passwd, 0, passwd.length);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
byte[] toHexAndSalt = new byte[64];
for (int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
digest.reset();
digest.update(toHexAndSalt, 0, 64);
digest.update(salt, 0, salt.length);
digest.doFinal(hashed, 0);
for (int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
return toHexAndSalt;
}
private static final byte[] HEX = new byte[] {
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
}

View File

@ -0,0 +1,100 @@
/*
* 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.socket.protocol.handshake;
import java.util.ArrayList;
import java.util.List;
import com.carrotsearch.hppc.IntArrayList;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController;
public class ClientCapabilities {
static ClientCapabilities createCapabilities(boolean cookie) {
ClientCapabilities caps = new ClientCapabilities();
caps.addStandard(StandardCaps.REDIRECT, 0);
caps.addStandard(StandardCaps.NOTIFICATION, 0);
caps.addStandard(StandardCaps.PAUSE_MENU, 0);
if (VoiceClientController.isClientSupported()) {
caps.addStandard(StandardCaps.VOICE, 0);
}
if (UpdateService.supported()) {
caps.addStandard(StandardCaps.UPDATE, 0);
}
if (WebViewOverlayController.supported() || WebViewOverlayController.fallbackSupported()) {
caps.addStandard(StandardCaps.WEBVIEW, 0);
}
if (cookie) {
caps.addStandard(StandardCaps.COOKIE, 0);
}
return caps;
}
static class ExtCapability {
final EaglercraftUUID uuid;
final int vers;
ExtCapability(EaglercraftUUID uuid, int vers) {
this.uuid = uuid;
this.vers = vers;
}
}
private int standardCaps = 0;
private IntArrayList standardCapsVers = new IntArrayList();
private List<ExtCapability> extendedCaps = new ArrayList<>();
int getStandardCaps() {
return standardCaps;
}
int[] getStandardCapsVers() {
return standardCapsVers.toArray();
}
ExtCapability[] getExtendedCaps() {
return extendedCaps.toArray(new ExtCapability[extendedCaps.size()]);
}
private void addStandard(int type, int... versions) {
int bit = (1 << type);
standardCapsVers.insert(Integer.bitCount(standardCaps & (bit - 1)), bits(versions));
standardCaps |= bit;
}
private void addExtended(EaglercraftUUID type, int... versions) {
extendedCaps.add(new ExtCapability(type, bits(versions)));
}
private int bits(int... versions) {
int bits = 0;
for(int i = 0; i < versions.length; ++i) {
bits |= (1 << versions[i]);
}
return bits;
}
}

View File

@ -0,0 +1,356 @@
/*
* 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.socket.protocol.handshake;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglerOutputStream;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.profile.GuiAuthenticationScreen;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.socket.RateLimitTracker;
import net.lax1dude.eaglercraft.v1_8.socket.WebSocketNetworkManager;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.GuiConnecting;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
public class HandshakerHandler {
static final Logger logger = LogManager.getLogger("HandshakerHandler");
protected final Minecraft mc;
protected final IWebSocketClient websocket;
protected final GuiConnecting parent;
protected final GuiScreen ret;
protected final String username;
protected final String password;
protected final boolean allowPlaintext;
protected final boolean enableCookies;
protected final byte[] cookieData;
protected HandshakerInstance handshaker;
protected boolean nicknameSelection = true;
protected int baseState = NEW;
protected WebSocketNetworkManager networkManager;
protected static final int NEW = 0, SENT_HANDSHAKE = 1, PROCESSING = 2, FINISHED = 3;
public HandshakerHandler(GuiConnecting parent, IWebSocketClient websocket, String username, String password,
boolean allowPlaintext, boolean enableCookies, byte[] cookieData) {
this.mc = GuiConnecting.getMC(parent);
this.websocket = websocket;
this.parent = parent;
this.ret = GuiConnecting.getPrevScreen(parent);
this.username = username;
this.password = password;
this.allowPlaintext = allowPlaintext;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
}
private static final int protocolV3 = 3;
private static final int protocolV4 = 4;
private static final int protocolV5 = 5;
public static byte[] getSPHandshakeProtocolData() {
try {
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeShort(3); // supported eagler protocols count
d.writeShort(protocolV3); // client supports v3
d.writeShort(protocolV4); // client supports v4
d.writeShort(protocolV5); // client supports v5
return bao.toByteArray();
}catch(IOException ex) {
throw new RuntimeException(ex);
}
}
public void tick() {
if(baseState == NEW) {
if(websocket.isClosed()) {
handleError("Connection Closed", null);
return;
}
baseState = SENT_HANDSHAKE;
beginHandshake();
}else if(baseState == SENT_HANDSHAKE) {
IWebSocketFrame frame = websocket.getNextBinaryFrame();
if(frame != null) {
byte[] data = frame.getByteArray();
handleServerHandshake(new PacketBuffer(Unpooled.buffer(data, data.length).writerIndex(data.length)));
}
}else if(baseState == PROCESSING) {
handshaker.tick();
}else if(baseState == FINISHED) {
if(networkManager != null) {
try {
networkManager.processReceivedPackets();
} catch (IOException e) {
}
}
}
}
protected void beginHandshake() {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_VERSION);
buffer.writeByte(2); // legacy protocol version
buffer.writeBytes(getSPHandshakeProtocolData()); // write supported eagler protocol versions
buffer.writeShort(1); // supported game protocols count
buffer.writeShort(47); // client supports 1.8 protocol
String clientBrand = EaglercraftVersion.projectForkName;
buffer.writeByte(clientBrand.length());
writeASCII(buffer, clientBrand);
String clientVers = EaglercraftVersion.projectOriginVersion;
buffer.writeByte(clientVers.length());
writeASCII(buffer, clientVers);
buffer.writeBoolean(password != null);
buffer.writeByte(username.length());
writeASCII(buffer, username);
websocket.send(buffer.toBytes());
}
protected static void writeASCII(PacketBuffer buffer, String str) {
for(int i = 0, l = str.length(); i < l; ++i) {
buffer.writeByte(str.charAt(i));
}
}
protected void handleServerHandshake(PacketBuffer packet) {
try {
int pktId = packet.readUnsignedByte();
switch(pktId) {
case HandshakePacketTypes.PROTOCOL_SERVER_VERSION:
handleServerVersion(packet);
break;
case HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH:
handleVersionMismatch(packet);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_ERROR:
handleServerError(packet, false);
break;
default:
handleError("connect.failed", new ChatComponentText("Unknown packet type " + pktId + " received"));
break;
}
}catch(Exception ex) {
handleError("connect.failed", new ChatComponentText("Invalid packet received"));
logger.error("Invalid packet received");
logger.error(ex);
}
}
protected void handleServerVersion(PacketBuffer packet) {
int protocolVersion = packet.readUnsignedShort();
if(protocolVersion != protocolV3 && protocolVersion != protocolV4 && protocolVersion != protocolV5) {
logger.info("Incompatible server version: {}", protocolVersion);
handleError("connect.failed", new ChatComponentText(protocolVersion < protocolV3 ? "Outdated Server" : "Outdated Client"));
return;
}
int gameVers = packet.readUnsignedShort();
if(gameVers != 47) {
logger.info("Incompatible minecraft protocol version: {}", gameVers);
handleError("connect.failed", new ChatComponentText("This server does not support 1.8!"));
return;
}
logger.info("Server protocol: {}", protocolVersion);
int msgLen = packet.readUnsignedByte();
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String pluginBrand = ArrayUtils.asciiString(dat);
msgLen = packet.readUnsignedByte();
dat = new byte[msgLen];
packet.readBytes(dat);
String pluginVersion = ArrayUtils.asciiString(dat);
logger.info("Server version: {}", pluginVersion);
logger.info("Server brand: {}", pluginBrand);
int authType = packet.readUnsignedByte();
int saltLength = (int)packet.readUnsignedShort() & 0xFFFF;
byte[] salt = new byte[saltLength];
packet.readBytes(salt);
if(protocolVersion >= protocolV5) {
nicknameSelection = packet.readBoolean();
}
baseState = PROCESSING;
switch(protocolVersion) {
case protocolV3:
handshaker = new HandshakerV3(this);
break;
case protocolV4:
handshaker = new HandshakerV4(this);
break;
case protocolV5:
handshaker = new HandshakerV5(this);
break;
}
handshaker.begin(pluginBrand, pluginVersion, authType, salt);
}
protected void handleVersionMismatch(PacketBuffer packet) {
StringBuilder protocols = new StringBuilder();
int c = packet.readUnsignedShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
protocols.append(", ");
}
protocols.append("v").append(packet.readUnsignedShort());
}
StringBuilder games = new StringBuilder();
c = packet.readUnsignedShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
games.append(", ");
}
games.append("mc").append(packet.readUnsignedShort());
}
logger.info("Incompatible client: v3/v4/v5 & mc47");
logger.info("Server supports: {}", protocols);
logger.info("Server supports: {}", games);
int msgLen = packet.readUnsignedByte();
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
handleError("connect.failed", new ChatComponentText(msg));
}
protected void handleServerError(PacketBuffer packet, boolean v3) {
int errCode = packet.readUnsignedByte();
int msgLen;
if(v3) {
msgLen = packet.readUnsignedShort();
if(msgLen == 0 && packet.readableBytes() == 65536) {
// workaround for bug in EaglerXBungee 1.2.7 and below
msgLen = 65536;
}
}else {
msgLen = packet.readUnsignedByte();
if(msgLen == 0 && packet.readableBytes() == 256) {
// workaround for bug in EaglerXBungee 1.2.7 and below
msgLen = 256;
}
}
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
if(errCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_BLOCKED) {
handleRatelimit(false, new ChatComponentText(msg));
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_LOCKED) {
handleRatelimit(true, new ChatComponentText(msg));
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED) {
handleAuthRequired(msg);
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE) {
handleError("connect.failed", v3 ? IChatComponent.Serializer.jsonToComponent(msg) : new ChatComponentText(msg));
}else {
handleError("connect.failed", new ChatComponentText("Server Error Code " + errCode + "\n" + msg));
}
}
protected void handleSuccess() {
if(baseState != FINISHED) {
baseState = FINISHED;
websocket.setEnableStringFrames(false);
websocket.clearStringFrames();
networkManager = new WebSocketNetworkManager(websocket);
networkManager.setPluginInfo(handshaker.pluginBrand, handshaker.pluginVersion, new ServerCapabilities(
handshaker.serverStandardCaps, handshaker.serverStandardCapVers, handshaker.extendedCaps));
mc.bungeeOutdatedMsgTimer = 80;
mc.clearTitles();
mc.getSession().update(handshaker.username, handshaker.uuid);
networkManager.setConnectionState(EnumConnectionState.PLAY);
new NetHandlerPlayClient(this.mc, ret, networkManager, this.mc.getSession().getProfile(),
GamePluginMessageProtocol.getByVersion(handshaker.getVersion()));
}
}
protected void handleServerRedirectTo(String address) {
mc.handleReconnectPacket(address);
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(ret);
}
}
protected void handleRatelimit(boolean locked, IChatComponent detail) {
if(locked) {
RateLimitTracker.registerLockOut(websocket.getCurrentURI());
}else {
RateLimitTracker.registerBlock(websocket.getCurrentURI());
}
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(ret));
}
}
protected void handleError(String message, IChatComponent detail) {
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(new GuiDisconnected(ret, message, detail != null ? detail : new ChatComponentText("")));
}
}
protected void handleAuthRequired(String message) {
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(new GuiAuthenticationScreen(parent, ret, message));
}
}
}

View File

@ -0,0 +1,224 @@
/*
* 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.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
public abstract class HandshakerInstance {
protected final HandshakerHandler handler;
protected String pluginBrand;
protected String pluginVersion;
protected String username;
protected EaglercraftUUID uuid;
protected int state = HandshakePacketTypes.STATE_NEW;
protected int serverStandardCaps;
protected byte[] serverStandardCapVers;
protected ObjectByteMap<EaglercraftUUID> extendedCaps;
public HandshakerInstance(HandshakerHandler handler) {
this.handler = handler;
}
protected void begin(String pluginBrand, String pluginVersion, int authType, byte[] salt) {
this.pluginBrand = pluginBrand;
this.pluginVersion = pluginVersion;
byte[] password = null;
if (handler.password != null) {
switch(authType) {
case HandshakePacketTypes.AUTH_METHOD_NONE:
break;
case HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256:
password = AuthTypes.applyEaglerSHA256(handler.password, salt);
break;
case HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256:
password = AuthTypes.applyAuthMeSHA256(handler.password, salt);
break;
case HandshakePacketTypes.AUTH_METHOD_PLAINTEXT:
if(!handler.allowPlaintext) {
handleError("disconnect.loginFailed", new ChatComponentText("Server attempted insecure plaintext authentication without user consent!"));
return;
}
password = handler.password.getBytes(StandardCharsets.UTF_8);
if(password.length > 255) {
handleError("disconnect.loginFailed", new ChatComponentText("Password is too long!"));
return;
}
break;
default:
handleError("disconnect.loginFailed", new ChatComponentText("Unknown auth method #" + authType + " requested"));
return;
}
}
sendClientRequestLogin(handler.username, "default", password, handler.enableCookies, handler.cookieData);
state = HandshakePacketTypes.STATE_CLIENT_LOGIN;
}
protected abstract int getVersion();
protected abstract void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie);
protected void tick() {
IWebSocketFrame frame;
while (state != HandshakePacketTypes.STATE_FINISHED
&& (frame = handler.websocket.getNextBinaryFrame()) != null) {
handleInboundPacket(frame.getByteArray());
}
if(handler.websocket.isClosed()) {
handleError("Connection Closed", (IChatComponent) null);
}
}
protected void handleInboundPacket(byte[] data) {
try {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer(data, data.length).writerIndex(data.length));
int pktId = buffer.readUnsignedByte();
switch(pktId) {
case HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN:
handleInboundServerAllowLogin(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN:
handleInboundServerDenyLogin(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN:
handleServerFinishLogin();
break;
case HandshakePacketTypes.PROTOCOL_SERVER_REDIRECT_TO:
handleInboundServerRedirectTo(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_ERROR:
handleInboundServerError(buffer);
break;
default:
handleError("connect.failed", "Unknown packet type " + pktId + " received");
break;
}
}catch(Exception ex) {
handler.handleError("connect.failed", new ChatComponentText("Invalid packet received"));
HandshakerHandler.logger.error("Invalid packet received");
HandshakerHandler.logger.error(ex);
}
}
protected void handleError(String message, String detail) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleError(message, IChatComponent.Serializer.jsonToComponent(detail));
}
protected void handleError(String message, IChatComponent detail) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleError(message, detail);
}
protected abstract void handleInboundServerAllowLogin(PacketBuffer buffer);
protected abstract void handleInboundServerDenyLogin(PacketBuffer buffer);
protected void handleServerAllowLogin(String username, EaglercraftUUID uuid, int serverStandardCaps,
byte[] serverStandardCapVers, ObjectByteMap<EaglercraftUUID> extendedCaps) {
if(state != HandshakePacketTypes.STATE_CLIENT_LOGIN) {
handleError("connect.failed", "Unexpected allow login packet in state " + state);
return;
}
this.username = username;
this.uuid = uuid;
this.serverStandardCaps = serverStandardCaps;
this.serverStandardCapVers = serverStandardCapVers;
this.extendedCaps = extendedCaps;
Map<String, byte[]> profileDataToSend = new HashMap<>();
if(getVersion() >= 4) {
byte[] arr = new byte[16];
ByteBuf buf = Unpooled.buffer(arr, 16);
buf.writeLong(EaglercraftVersion.clientBrandUUID.msb);
buf.writeLong(EaglercraftVersion.clientBrandUUID.lsb);
profileDataToSend.put("brand_uuid_v1", arr);
}
byte[] packetSkin = EaglerProfile.getSkinPacket(getVersion());
if(packetSkin.length > 0xFFFF) {
handleError("connect.failed", new ChatComponentText("Skin packet is too long: " + packetSkin.length));
return;
}
profileDataToSend.put(getVersion() >= 4 ? "skin_v2" : "skin_v1", packetSkin);
byte[] packetCape = EaglerProfile.getCapePacket();
if(packetCape.length > 0xFFFF) {
handleError("connect.failed", new ChatComponentText("Cape packet is too long: " + packetCape.length));
return;
}
profileDataToSend.put("cape_v1", packetCape);
byte[] packetSignatureData = UpdateService.getClientSignatureData();
if(packetSignatureData != null) {
profileDataToSend.put("update_cert_v1", packetSignatureData);
}
sendClientProfileData(profileDataToSend);
sendFinishLogin();
state = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
}
protected abstract void sendClientProfileData(Map<String, byte[]> profileDataToSend);
protected abstract void sendFinishLogin();
protected void handleServerFinishLogin() {
if(state != HandshakePacketTypes.STATE_CLIENT_COMPLETE) {
handleError("connect.failed", "Unexpected finish login packet in state " + state);
return;
}
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleSuccess();
}
protected abstract void handleInboundServerRedirectTo(PacketBuffer buffer);
protected void handleServerRedirectTo(String address) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleServerRedirectTo(address);
}
protected abstract void handleInboundServerError(PacketBuffer buffer);
}

View File

@ -0,0 +1,106 @@
/*
* 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.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.IChatComponent;
public class HandshakerV3 extends HandshakerInstance {
public HandshakerV3(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 3;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, ServerCapabilities.VIRTUAL_V3_SERVER_CAPS,
ServerCapabilities.VIRTUAL_V3_SERVER_CAPS_VERS, null);
}
@Override
protected void handleInboundServerDenyLogin(PacketBuffer buffer) {
byte[] dat = new byte[buffer.readUnsignedShort()];
buffer.readBytes(dat);
handleError("disconnect.loginFailed",
IChatComponent.Serializer.jsonToComponent(new String(dat, StandardCharsets.UTF_8)));
}
@Override
protected void sendClientProfileData(Map<String, byte[]> profileDataToSend) {
for(Map.Entry<String, byte[]> etr : profileDataToSend.entrySet()) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
String profileDataType = etr.getKey();
buffer.writeByte(profileDataType.length());
HandshakerHandler.writeASCII(buffer, profileDataType);
byte[] data = etr.getValue();
buffer.writeShort(data.length);
buffer.writeBytes(data);
handler.websocket.send(buffer.toBytes());
}
}
@Override
protected void sendFinishLogin() {
handler.websocket.send(new byte[] { (byte) HandshakePacketTypes.PROTOCOL_CLIENT_FINISH_LOGIN });
}
@Override
protected void handleInboundServerRedirectTo(PacketBuffer buffer) {
handleError("disconnect.loginFailed", "Unexpected login redirect packet");
}
@Override
protected void handleInboundServerError(PacketBuffer buffer) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleServerError(buffer, true);
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.socket.protocol.handshake;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.minecraft.network.PacketBuffer;
public class HandshakerV4 extends HandshakerV3 {
public HandshakerV4(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 4;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
buffer.writeBoolean(enableCookies);
if(enableCookies && cookie != null) {
buffer.writeByte(cookie.length);
buffer.writeBytes(cookie);
}else {
buffer.writeByte(0);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, ServerCapabilities.VIRTUAL_V4_SERVER_CAPS,
ServerCapabilities.VIRTUAL_V4_SERVER_CAPS_VERS, null);
}
@Override
protected void sendClientProfileData(Map<String, byte[]> profileDataToSend) {
List<Entry<String,byte[]>> toSend = new ArrayList<>(profileDataToSend.entrySet());
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
while(!toSend.isEmpty()) {
int sendLen = 2;
buffer.writerIndex(0);
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
buffer.writeByte(0); // will be replaced
int packetCount = 0;
while(!toSend.isEmpty() && packetCount < 255) {
Entry<String,byte[]> etr = toSend.get(toSend.size() - 1);
int i = 3 + etr.getKey().length() + etr.getValue().length;
if(sendLen + i < 0xFF00) {
String profileDataType = etr.getKey();
buffer.writeByte(profileDataType.length());
HandshakerHandler.writeASCII(buffer, profileDataType);
byte[] data = etr.getValue();
buffer.writeShort(data.length);
buffer.writeBytes(data);
toSend.remove(toSend.size() - 1);
++packetCount;
}else {
break;
}
}
byte[] send = buffer.toBytes();
send[1] = (byte)packetCount;
handler.websocket.send(send);
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import com.carrotsearch.hppc.ObjectByteHashMap;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.ClientCapabilities.ExtCapability;
import net.minecraft.network.PacketBuffer;
public class HandshakerV5 extends HandshakerV4 {
public HandshakerV5(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 5;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
if(handler.nicknameSelection) {
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
}else {
buffer.writeByte(0);
}
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
buffer.writeBoolean(enableCookies);
if(enableCookies && cookie != null) {
buffer.writeByte(cookie.length);
buffer.writeBytes(cookie);
}else {
buffer.writeByte(0);
}
ClientCapabilities caps = ClientCapabilities.createCapabilities(enableCookies);
buffer.writeVarIntToBuffer(caps.getStandardCaps());
int[] vers = caps.getStandardCapsVers();
for(int i = 0; i < vers.length; ++i) {
buffer.writeVarIntToBuffer(vers[i]);
}
ExtCapability[] extVers = caps.getExtendedCaps();
buffer.writeByte(extVers.length);
for(int i = 0; i < extVers.length; ++i) {
ExtCapability extCap = extVers[i];
buffer.writeLong(extCap.uuid.msb);
buffer.writeLong(extCap.uuid.lsb);
buffer.writeVarIntToBuffer(extCap.vers);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
int standardCaps = buffer.readVarIntFromBuffer();
byte[] standardCapsVers = new byte[Integer.bitCount(standardCaps)];
buffer.readBytes(standardCapsVers);
int extCaps = buffer.readUnsignedByte();
ObjectByteMap<EaglercraftUUID> extCapsMap = null;
if(extCaps > 0) {
extCapsMap = new ObjectByteHashMap<>(extCaps);
for (int i = 0; i < extCaps; ++i) {
extCapsMap.put(new EaglercraftUUID(buffer.readLong(), buffer.readLong()), buffer.readByte());
}
}
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, standardCaps, standardCapsVers, extCapsMap);
}
@Override
protected void handleInboundServerRedirectTo(PacketBuffer buffer) {
byte[] urlLen = new byte[buffer.readShort()];
buffer.readBytes(urlLen);
handleServerRedirectTo(new String(urlLen, StandardCharsets.UTF_8));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.socket.protocol.handshake;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
public class ServerCapabilities {
static final int VIRTUAL_V3_SERVER_CAPS = (1 << StandardCaps.UPDATE) | (1 << StandardCaps.VOICE);
static final byte[] VIRTUAL_V3_SERVER_CAPS_VERS = new byte[] { 0, 0 };
static final int VIRTUAL_V4_SERVER_CAPS = (1 << StandardCaps.UPDATE) | (1 << StandardCaps.VOICE)
| (1 << StandardCaps.REDIRECT) | (1 << StandardCaps.NOTIFICATION) | (1 << StandardCaps.PAUSE_MENU)
| (1 << StandardCaps.WEBVIEW) | (1 << StandardCaps.COOKIE);
static final byte[] VIRTUAL_V4_SERVER_CAPS_VERS = new byte[] { 0, 0, 0, 0, 0, 0 };
private final int standardCaps;
private final byte[] standardCapVers;
private final ObjectByteMap<EaglercraftUUID> extendedCaps;
public ServerCapabilities(int standardCaps, byte[] standardCapVers, ObjectByteMap<EaglercraftUUID> extendedCaps) {
this.standardCaps = standardCaps;
this.standardCapVers = standardCapVers;
this.extendedCaps = extendedCaps;
}
public boolean hasCapability(int cap, int ver) {
int bit = 1 << cap;
if((standardCaps & bit) != 0) {
int versIndex = Integer.bitCount(standardCaps & (bit - 1));
if(versIndex < standardCapVers.length) {
return (standardCapVers[versIndex] & 0xFF) >= ver;
}
}
return false;
}
public int getCapability(int cap) {
int bit = 1 << cap;
if((standardCaps & bit) != 0) {
int versIndex = Integer.bitCount(standardCaps & (bit - 1));
if(versIndex < standardCapVers.length) {
return standardCapVers[versIndex] & 0xFF;
}
}
return -1;
}
public int getExtCapability(EaglercraftUUID uuid) {
if(extendedCaps != null) {
int idx = extendedCaps.indexOf(uuid);
if(idx >= 0) {
return (int) extendedCaps.indexGet(idx) & 0xFF;
}
}
return -1;
}
public static ServerCapabilities getLAN() {
return new ServerCapabilities(VIRTUAL_V3_SERVER_CAPS, VIRTUAL_V3_SERVER_CAPS_VERS, null);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.socket.protocol.handshake;
public class StandardCaps {
public static final int UPDATE = 0;
public static final int VOICE = 1;
public static final int REDIRECT = 2;
public static final int NOTIFICATION = 3;
public static final int PAUSE_MENU = 4;
public static final int WEBVIEW = 5;
public static final int COOKIE = 6;
// reserved
public static final int EAGLER_IP = 7;
}

View File

@ -0,0 +1,160 @@
/*
* 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.socket.protocol.message;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleInputBufferImpl;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
public class InjectedMessageController extends MessageController {
private static final Logger logger = LogManager.getLogger("InjectedMessageController");
private final ReusableByteArrayInputStream byteInputStreamSingleton = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream byteOutputStreamSingleton = new ReusableByteArrayOutputStream();
private final SimpleInputBufferImpl inputStreamSingleton = new SimpleInputBufferImpl(byteInputStreamSingleton);
private final SimpleOutputBufferImpl outputStreamSingleton = new SimpleOutputBufferImpl(byteOutputStreamSingleton);
public interface IBinarySendFunction {
void sendBinaryFrame(byte[] contents);
}
private final IBinarySendFunction send;
public InjectedMessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction,
IBinarySendFunction send) {
super(protocol, handler, direction);
this.send = send;
}
public boolean handlePacket(byte[] data, int offset) throws IOException {
if(data.length - offset > 1 && data[offset] == (byte) 0xEE) {
GameMessagePacket pkt;
byteInputStreamSingleton.feedBuffer(data, offset);
inputStreamSingleton.readByte();
if(data[offset + 1] == (byte) 0xFF) {
inputStreamSingleton.readByte();
int count = inputStreamSingleton.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputStreamSingleton.readVarInt();
inputStreamSingleton.setToByteArrayReturns(j - 1);
k = byteInputStreamSingleton.getReaderIndex() + j;
if(j < 0 || j > inputStreamSingleton.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputStreamSingleton.available());
}
pkt = protocol.readPacketV5(receiveDirection, inputStreamSingleton);
if(byteInputStreamSingleton.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + byteInputStreamSingleton.getReaderIndex() - k) + " != " + j);
}
handlePacket(pkt);
}
if(inputStreamSingleton.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputStreamSingleton.available() + " bytes)");
}
byteOutputStreamSingleton.feedBuffer(null);
return true;
}
inputStreamSingleton.setToByteArrayReturns(data.length - offset - 2);
pkt = protocol.readPacketV5(receiveDirection, inputStreamSingleton);
if(byteInputStreamSingleton.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
byteOutputStreamSingleton.feedBuffer(null);
handlePacket(pkt);
return true;
}
return false;
}
@Override
protected void writePacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 2;
byteOutputStreamSingleton.feedBuffer(len == 1 ? new byte[64] : new byte[len]);
byteOutputStreamSingleton.write(0xEE);
protocol.writePacketV5(sendDirection, outputStreamSingleton, packet);
byte[] data = byteOutputStreamSingleton.returnBuffer();
byteOutputStreamSingleton.feedBuffer(null);
if(len != 1 && data.length != len) {
logger.warn("Packet " + packet.getClass().getSimpleName() + " was the wrong length after serialization, "
+ data.length + " != " + len);
}
send.sendBinaryFrame(data);
}
@Override
protected void writeMultiPacket(List<GameMessagePacket> packets) throws IOException {
int total = packets.size();
byte[][] buffer = new byte[total][];
byte[] dat;
for(int i = 0; i < total; ++i) {
GameMessagePacket packet = packets.get(i);
int len = packet.length() + 2;
byteOutputStreamSingleton.feedBuffer(len == 1 ? new byte[64] : new byte[len]);
byteOutputStreamSingleton.write(0xEE);
protocol.writePacketV5(sendDirection, outputStreamSingleton, packet);
dat = byteOutputStreamSingleton.returnBuffer();
byteOutputStreamSingleton.feedBuffer(null);
if(len != 1 && dat.length != len) {
logger.warn("Packet " + packet.getClass().getSimpleName()
+ " was the wrong length after serialization, " + dat.length + " != " + len);
}
buffer[i] = dat;
}
int start = 0;
int i, j, sendCount, totalLen, lastLen;
while(total > start) {
sendCount = 0;
totalLen = 0;
do {
i = buffer[start + sendCount].length - 1;
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
send.sendBinaryFrame(buffer[start++]);
continue;
}
byteOutputStreamSingleton.feedBuffer(new byte[2 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)]);
outputStreamSingleton.writeShort(0xEEFF);
outputStreamSingleton.writeVarInt(sendCount);
for(j = 0; j < sendCount; ++j) {
dat = buffer[start++];
i = dat.length - 1;
outputStreamSingleton.writeVarInt(i);
outputStreamSingleton.write(dat, 1, i);
}
send.sendBinaryFrame(byteOutputStreamSingleton.returnBuffer());
byteOutputStreamSingleton.feedBuffer(null);
}
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2024-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.socket.protocol.message;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.minecraft.network.PacketBuffer;
public class LegacyMessageController extends MessageController {
private static final Logger logger = LogManager.getLogger("LegacyMessageController");
public interface IPluginMessageSendFunction {
void sendPluginMessage(String channel, PacketBuffer contents);
}
private final PacketBufferInputWrapper inputStream = new PacketBufferInputWrapper(null);
private final PacketBufferOutputWrapper outputStream = new PacketBufferOutputWrapper(null);
private final IPluginMessageSendFunction send;
public LegacyMessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction,
IPluginMessageSendFunction send) {
super(protocol, handler, direction);
this.send = send;
}
public boolean handlePacket(String channel, PacketBuffer data) throws IOException {
GameMessagePacket pkt;
if(protocol.ver >= 4 && data.readableBytes() > 0 && data.getByte(data.readerIndex()) == (byte) 0xFF
&& channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
data.readByte();
inputStream.buffer = data;
int count = inputStream.readVarInt();
for(int i = 0, j, k, l; i < count; ++i) {
j = data.readVarIntFromBuffer();
k = data.readerIndex() + j;
l = data.writerIndex();
if(j < 0 || k > l) {
throw new IOException("Packet fragment is too long: " + j + " > " + data.readableBytes());
}
data.writerIndex(k);
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null) {
handlePacket(pkt);
}else {
logger.warn("Could not read packet fragment {} of {}, unknown packet", count, i);
}
if(data.readerIndex() != k) {
logger.warn("Packet fragment {} was the wrong length: {} != {}",
(pkt != null ? pkt.getClass().getSimpleName() : "unknown"), j + data.readerIndex() - k, j);
data.readerIndex(k);
}
data.writerIndex(l);
}
if(data.readableBytes() > 0) {
logger.warn("Leftover data after reading multi-packet! ({} bytes)", data.readableBytes());
}
inputStream.buffer = null;
return true;
}
inputStream.buffer = data;
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null && inputStream.available() > 0) {
logger.warn("Leftover data after reading packet {}! ({} bytes)", pkt.getClass().getSimpleName(), inputStream.available());
}
inputStream.buffer = null;
if(pkt != null) {
handlePacket(pkt);
return true;
}else {
return false;
}
}
@Override
protected void writePacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
String chan = protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && (protocol.ver > 3 || j + 1 != len)) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
send.sendPluginMessage(chan, buf);
}
@Override
protected void writeMultiPacket(List<GameMessagePacket> packets) throws IOException {
int total = packets.size();
PacketBuffer[] toSend = new PacketBuffer[total];
for(int i = 0; i < total; ++i) {
GameMessagePacket packet = packets.get(i);
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && (protocol.ver > 3 || j + 1 != len)) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
toSend[i] = buf;
}
int start = 0;
int i, j, sendCount, totalLen, lastLen;
while(total > start) {
sendCount = 0;
totalLen = 0;
do {
i = toSend[start + sendCount].readableBytes();
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
send.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, toSend[start++]);
continue;
}
PacketBuffer sendBuffer = new PacketBuffer(
Unpooled.buffer(1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)));
sendBuffer.writerIndex(0);
sendBuffer.writeByte(0xFF);
sendBuffer.writeVarIntToBuffer(sendCount);
for(j = 0; j < sendCount; ++j) {
PacketBuffer dat = toSend[start++];
sendBuffer.writeVarIntToBuffer(dat.readableBytes());
sendBuffer.writeBytes(dat);
}
send.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, sendBuffer);
}
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.socket.protocol.message;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
public abstract class MessageController {
private static final Logger logger = LogManager.getLogger("MessageController");
protected final GamePluginMessageProtocol protocol;
protected final GameMessageHandler handler;
protected final int sendDirection;
protected final int receiveDirection;
protected List<GameMessagePacket> sendQueue;
public MessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction) {
this.protocol = protocol;
this.handler = handler;
this.sendDirection = direction;
this.receiveDirection = direction == GamePluginMessageConstants.CLIENT_TO_SERVER
? GamePluginMessageConstants.SERVER_TO_CLIENT
: GamePluginMessageConstants.CLIENT_TO_SERVER;
this.sendQueue = protocol.ver >= 4 && !EagRuntime.getConfiguration().isEaglerNoDelay()
? new ArrayList<>() : null;
}
public GamePluginMessageProtocol getProtocol() {
return protocol;
}
public boolean isSendQueueEnabled() {
return sendQueue != null;
}
public void sendPacket(GameMessagePacket packet) {
if(sendQueue != null) {
sendQueue.add(packet);
}else {
try {
writePacket(packet);
} catch (IOException ex) {
throw new RuntimeException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
}
}
protected abstract void writePacket(GameMessagePacket packet) throws IOException;
public void flush() {
if(sendQueue != null && !sendQueue.isEmpty()) {
try {
writeMultiPacket(sendQueue);
} catch (IOException ex) {
throw new RuntimeException("Failed to serialize packet multi-packet!", ex);
}
if(sendQueue.size() < 64) {
sendQueue.clear();
}else {
sendQueue = new ArrayList<>();
}
}
}
protected abstract void writeMultiPacket(List<GameMessagePacket> packets) throws IOException;
protected void handlePacket(GameMessagePacket packet) {
try {
packet.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", packet.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
}
}

View File

@ -14,7 +14,7 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.DataInputStream;
import java.io.EOFException;

View File

@ -14,7 +14,7 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.IOException;
import java.io.OutputStream;

View File

@ -19,7 +19,7 @@ package net.lax1dude.eaglercraft.v1_8.sp.gui;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformWebRTC;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.socket.ConnectionHandshake;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.HandshakerHandler;
import net.lax1dude.eaglercraft.v1_8.sp.lan.LANClientNetworkManager;
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager;
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayServer;
@ -101,8 +101,11 @@ public class GuiScreenLANConnecting extends GuiScreen {
if(++renderCount > 1) {
RelayServerSocket sock;
if(relay == null) {
sock = RelayManager.relayManager.getWorkingRelay((str) -> ls.resetProgressAndMessage("Connecting: " + str), 0x02, code);
ls.resetProgressAndMessage("Connecting to '" + code + "'...");
sock = RelayManager.relayManager.getWorkingRelay((str) -> ls.displayLoadingString("Connecting: " + str), 0x02, code);
}else {
ls.resetProgressAndMessage("Connecting to '" + code + "'...");
ls.displayLoadingString("Connecting: " + relay.address);
sock = RelayManager.relayManager.connectHandshake(relay, 0x02, code);
}
if(sock == null) {
@ -125,7 +128,7 @@ public class GuiScreenLANConnecting extends GuiScreen {
networkManager.setNetHandler(new NetHandlerSingleplayerLogin(networkManager, mc, parent));
networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(),
EaglerProfile.getSkinPacket(3), EaglerProfile.getCapePacket(),
ConnectionHandshake.getSPHandshakeProtocolData(), EaglercraftVersion.clientBrandUUID));
HandshakerHandler.getSPHandshakeProtocolData(), EaglercraftVersion.clientBrandUUID));
}
}
}

View File

@ -21,7 +21,7 @@ import java.io.IOException;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.socket.ConnectionHandshake;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.HandshakerHandler;
import net.lax1dude.eaglercraft.v1_8.sp.SingleplayerServerController;
import net.lax1dude.eaglercraft.v1_8.sp.socket.ClientIntegratedServerNetworkManager;
import net.lax1dude.eaglercraft.v1_8.sp.socket.NetHandlerSingleplayerLogin;
@ -94,7 +94,7 @@ public class GuiScreenSingleplayerConnecting extends GuiScreen {
this.networkManager.setNetHandler(new NetHandlerSingleplayerLogin(this.networkManager, this.mc, this.menu));
this.networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(),
EaglerProfile.getSkinPacket(3), EaglerProfile.getCapePacket(),
ConnectionHandshake.getSPHandshakeProtocolData(), EaglercraftVersion.clientBrandUUID));
HandshakerHandler.getSPHandshakeProtocolData(), EaglercraftVersion.clientBrandUUID));
}
try {
this.networkManager.processReceivedPackets();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2022-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -366,6 +366,10 @@ public class LANClientNetworkManager extends EaglercraftNetworkManager {
}
}
if(injectedController != null && injectedController.handlePacket(fullData, off)) {
continue;
}
ByteBuf nettyBuffer = Unpooled.buffer(fullData, fullData.length);
nettyBuffer.writerIndex(fullData.length);
nettyBuffer.readerIndex(off);
@ -399,6 +403,32 @@ public class LANClientNetworkManager extends EaglercraftNetworkManager {
}
}
@Override
public void injectRawFrame(byte[] data) {
if(!isChannelOpen()) {
logger.error("Frame was injected on a closed connection");
return;
}
int len = data.length;
int fragmentSizeN1 = fragmentSize - 1;
if(len > fragmentSizeN1) {
int idx = 0;
do {
int readLen = len > fragmentSizeN1 ? fragmentSizeN1 : len;
byte[] frag = new byte[readLen + 1];
System.arraycopy(data, idx, frag, 1, readLen);
idx += readLen;
len -= readLen;
frag[0] = len == 0 ? (byte)0 : (byte)1;
PlatformWebRTC.clientLANSendPacket(frag);
}while(len > 0);
}else {
byte[] bytes = new byte[len + 1];
System.arraycopy(data, 0, bytes, 1, len);
PlatformWebRTC.clientLANSendPacket(bytes);
}
}
@Override
public void closeChannel(IChatComponent reason) {
if(!PlatformWebRTC.clientLANClosed()) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2023-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2023-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -23,6 +23,7 @@ import java.util.List;
import com.google.common.collect.Lists;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EagUtils;
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.minecraft.entity.player.EntityPlayer;
@ -33,8 +34,7 @@ import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.WorldServer;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldSettings.GameType;
import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedCapeService;
import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedSkinService;
import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedTextureService;
import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService;
public class EaglerMinecraftServer extends MinecraftServer {
@ -48,8 +48,7 @@ public class EaglerMinecraftServer extends MinecraftServer {
protected WorldSettings newWorldSettings;
protected boolean paused;
protected EaglerSaveHandler saveHandler;
protected IntegratedSkinService skinService;
protected IntegratedCapeService capeService;
protected IntegratedTextureService textureService;
protected IntegratedVoiceService voiceService;
private long lastTPSUpdate = 0l;
@ -67,25 +66,22 @@ public class EaglerMinecraftServer extends MinecraftServer {
super(world);
Bootstrap.register();
this.saveHandler = new EaglerSaveHandler(savesDir, world);
this.skinService = new IntegratedSkinService(WorldsDB.newVFile(saveHandler.getWorldDirectory(), "eagler/skulls"));
this.capeService = new IntegratedCapeService();
EaglerPlayerList playerList = new EaglerPlayerList(this, viewDistance);
this.textureService = new IntegratedTextureService(playerList,
WorldsDB.newVFile(saveHandler.getWorldDirectory(), "eagler/skulls"));
this.voiceService = null;
this.setServerOwner(owner);
logger.info("server owner: " + owner);
this.setDemo(demo);
this.canCreateBonusChest(currentWorldSettings != null && currentWorldSettings.isBonusChestEnabled());
this.setBuildLimit(256);
this.setConfigManager(new EaglerPlayerList(this, viewDistance));
this.setConfigManager(playerList);
this.newWorldSettings = currentWorldSettings;
this.paused = false;
}
public IntegratedSkinService getSkinService() {
return skinService;
}
public IntegratedCapeService getCapeService() {
return capeService;
public IntegratedTextureService getTextureService() {
return textureService;
}
public IntegratedVoiceService getVoiceService() {
@ -166,12 +162,14 @@ public class EaglerMinecraftServer extends MinecraftServer {
this.currentTime += 50l;
this.tick();
++counterTicksPerSecond;
} else if (!singleThreadMode) {
EagUtils.sleep(1);
}
}
}
public void updateTimeLightAndEntities() {
this.skinService.flushCache();
this.textureService.flushCache();
super.updateTimeLightAndEntities();
}

View File

@ -44,8 +44,5 @@ public class EaglerPlayerList extends ServerConfigurationManager {
public void playerLoggedOut(EntityPlayerMP playerIn) {
super.playerLoggedOut(playerIn);
EaglerMinecraftServer svr = (EaglerMinecraftServer)getServerInstance();
svr.skinService.unregisterPlayer(playerIn.getUniqueID());
svr.capeService.unregisterPlayer(playerIn.getUniqueID());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 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
@ -17,35 +17,56 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
public class CustomSkullData {
public String skinURL;
public long lastHit;
public SkinPacketVersionCache skinData;
protected long lastHit;
protected byte[] textureDataV3;
protected byte[] textureDataV4;
public CustomSkullData(String skinURL, byte[] skinData) {
this.skinURL = skinURL;
this.lastHit = EagRuntime.steadyTimeMillis();
public CustomSkullData(byte[] skinData) {
if(skinData.length != 16384) {
byte[] fixed = new byte[16384];
System.arraycopy(skinData, 0, fixed, 0, skinData.length > fixed.length ? fixed.length : skinData.length);
skinData = fixed;
}
this.skinData = SkinPacketVersionCache.createCustomV3(0l, 0l, 0, skinData);
textureDataV3 = skinData;
lastHit = EagRuntime.steadyTimeMillis();
}
public byte[] getFullSkin() {
return ((SPacketOtherSkinCustomV3EAG)skinData.getV3()).customSkin;
return textureDataV3;
}
public GameMessagePacket getSkinPacket(EaglercraftUUID uuid, GamePluginMessageProtocol protocol) {
return SkinPacketVersionCache.rewriteUUID(skinData.get(protocol), uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
public GameMessagePacket getSkin(long uuidMost, long uuidLeast, GamePluginMessageProtocol protocol) {
switch(protocol) {
case V3:
return new SPacketOtherSkinCustomV3EAG(uuidMost, uuidLeast, 0xFF, textureDataV3);
case V4:
if(textureDataV4 == null) {
textureDataV4 = SkinPacketVersionCache.convertToV4Raw(textureDataV3);
}
return new SPacketOtherSkinCustomV4EAG(uuidMost, uuidLeast, 0xFF, textureDataV4);
default:
throw new IllegalStateException();
}
}
public GameMessagePacket getSkinV5(int requestId, GamePluginMessageProtocol protocol) {
if(protocol.ver >= 5) {
if(textureDataV4 == null) {
textureDataV4 = SkinPacketVersionCache.convertToV4Raw(textureDataV3);
}
return new SPacketOtherSkinCustomV5EAG(requestId, 0xFF, textureDataV4);
}else {
throw new IllegalStateException();
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.sp.server.skins;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2;
import net.lax1dude.eaglercraft.v1_8.sp.server.WorldsDB;
public class CustomSkullLoader {
private static final byte[] skullNotFoundTexture = new byte[4096];
static {
for(int y = 0; y < 16; ++y) {
for(int x = 0; x < 64; ++x) {
int i = (y << 8) | (x << 2);
byte j = ((x + y) & 1) == 1 ? (byte)255 : 0;
skullNotFoundTexture[i] = (byte)255;
skullNotFoundTexture[i + 1] = j;
skullNotFoundTexture[i + 2] = 0;
skullNotFoundTexture[i + 3] = j;
}
}
}
private final VFile2 folder;
private final Map<String,CustomSkullData> customSkulls = new HashMap<>(64);
private long lastFlush = 0l;
public CustomSkullLoader(VFile2 folder) {
this.folder = folder;
}
private CustomSkullData loadSkullData0(String urlStr) {
byte[] data = WorldsDB.newVFile(folder, urlStr).getAllBytes();
if(data == null) {
return new CustomSkullData(skullNotFoundTexture);
}else {
return new CustomSkullData(data);
}
}
public CustomSkullData loadSkullData(String url) {
CustomSkullData sk = customSkulls.get(url);
if(sk == null) {
customSkulls.put(url, sk = loadSkullData0(url));
}else {
sk.lastHit = EagRuntime.steadyTimeMillis();
}
return sk;
}
private static final String hex = "0123456789abcdef";
public String installNewSkull(byte[] skullData) {
// set to 16384 to save a full 64x64 skin
if(skullData.length > 4096) {
byte[] tmp = skullData;
skullData = new byte[4096];
System.arraycopy(tmp, 0, skullData, 0, 4096);
}
SHA1Digest sha = new SHA1Digest();
sha.update(skullData, 0, skullData.length);
byte[] hash = new byte[20];
sha.doFinal(hash, 0);
char[] hashText = new char[40];
for(int i = 0; i < 20; ++i) {
hashText[i << 1] = hex.charAt((hash[i] & 0xF0) >> 4);
hashText[(i << 1) + 1] = hex.charAt(hash[i] & 0x0F);
}
String str = "skin-" + new String(hashText) + ".bmp";
customSkulls.put(str, new CustomSkullData(skullData));
WorldsDB.newVFile(folder, str).setAllBytes(skullData);
return str;
}
public void flushCache() {
long cur = EagRuntime.steadyTimeMillis();
if(cur - lastFlush > 300000l) {
lastFlush = cur;
Iterator<CustomSkullData> customSkullsItr = customSkulls.values().iterator();
while(customSkullsItr.hasNext()) {
if(cur - customSkullsItr.next().lastHit > 900000l) {
customSkullsItr.remove();
}
}
}
}
}

View File

@ -1,62 +0,0 @@
/*
* 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.
*
*/
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
public class IntegratedCapePackets {
public static final int PACKET_MY_CAPE_PRESET = 0x01;
public static final int PACKET_MY_CAPE_CUSTOM = 0x02;
public static void registerEaglerPlayer(EaglercraftUUID clientUUID, byte[] bs, IntegratedCapeService capeService) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
GameMessagePacket generatedPacket;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
case PACKET_MY_CAPE_PRESET:
if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset cape packet");
}
generatedPacket = new SPacketOtherCapePresetEAG(clientUUID.msb, clientUUID.lsb, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break;
case PACKET_MY_CAPE_CUSTOM:
if(bs.length != 1174) {
throw new IOException("Invalid length " + bs.length + " for custom cape packet");
}
byte[] capePixels = new byte[bs.length - 1];
System.arraycopy(bs, 1, capePixels, 0, capePixels.length);
generatedPacket = new SPacketOtherCapeCustomEAG(clientUUID.msb, clientUUID.lsb, capePixels);
break;
default:
throw new IOException("Unknown skin packet type: " + packetType);
}
capeService.registerEaglercraftPlayer(clientUUID, generatedPacket);
}
public static void registerEaglerPlayerFallback(EaglercraftUUID clientUUID, IntegratedCapeService capeService) {
capeService.registerEaglercraftPlayer(clientUUID, new SPacketOtherCapePresetEAG(clientUUID.msb, clientUUID.lsb, 0));
}
}

View File

@ -1,65 +0,0 @@
/*
* 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.
*
*/
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
import net.minecraft.entity.player.EntityPlayerMP;
public class IntegratedCapeService {
public static final Logger logger = LogManager.getLogger("IntegratedCapeService");
public static final int masterRateLimitPerPlayer = 250;
private final Map<EaglercraftUUID, GameMessagePacket> capesCache = new HashMap<>();
public void processLoginPacket(byte[] packetData, EntityPlayerMP sender) {
try {
IntegratedCapePackets.registerEaglerPlayer(sender.getUniqueID(), packetData, this);
} catch (IOException e) {
logger.error("Invalid skin data packet recieved from player {}!", sender.getName());
logger.error(e);
sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin data packet recieved!");
}
}
public void registerEaglercraftPlayer(EaglercraftUUID playerUUID, GameMessagePacket capePacket) {
capesCache.put(playerUUID, capePacket);
}
public void processGetOtherCape(EaglercraftUUID searchUUID, EntityPlayerMP sender) {
GameMessagePacket maybeCape = capesCache.get(searchUUID);
if(maybeCape == null) {
maybeCape = new SPacketOtherCapePresetEAG(searchUUID.msb, searchUUID.lsb, 0);
}
sender.playerNetServerHandler.sendEaglerMessage(maybeCape);
}
public void unregisterPlayer(EaglercraftUUID playerUUID) {
synchronized(capesCache) {
capesCache.remove(playerUUID);
}
}
}

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
public class IntegratedSkinPackets {
public static final int PACKET_MY_SKIN_PRESET = 0x01;
public static final int PACKET_MY_SKIN_CUSTOM = 0x02;
public static void registerEaglerPlayer(EaglercraftUUID clientUUID, byte[] bs, IntegratedSkinService skinService,
int protocolVers) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
GameMessagePacket generatedPacketV3 = null;
GameMessagePacket generatedPacketV4 = null;
int skinModel = -1;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
case PACKET_MY_SKIN_PRESET:
if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset skin packet");
}
generatedPacketV3 = generatedPacketV4 = new SPacketOtherSkinPresetEAG(clientUUID.msb, clientUUID.lsb,
(bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break;
case PACKET_MY_SKIN_CUSTOM:
if(protocolVers <= 3) {
byte[] pixels = new byte[16384];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChestV3(pixels);
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV3 = new SPacketOtherSkinCustomV3EAG(clientUUID.msb, clientUUID.lsb, (skinModel = (int)bs[1] & 0xFF), pixels);
}else {
byte[] pixels = new byte[12288];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChestV4(pixels);
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV4 = new SPacketOtherSkinCustomV4EAG(clientUUID.msb, clientUUID.lsb, (skinModel = (int)bs[1] & 0xFF), pixels);
}
break;
default:
throw new IOException("Unknown skin packet type: " + packetType);
}
skinService.processPacketPlayerSkin(clientUUID, new SkinPacketVersionCache(generatedPacketV3, generatedPacketV4), skinModel);
}
public static void registerEaglerPlayerFallback(EaglercraftUUID clientUUID, IntegratedSkinService skinService) throws IOException {
int skinModel = (clientUUID.hashCode() & 1) != 0 ? 1 : 0;
skinService.processPacketPlayerSkin(clientUUID, SkinPacketVersionCache.createPreset(clientUUID.msb, clientUUID.lsb, skinModel), skinModel);
}
public static void setAlphaForChestV3(byte[] skin64x64) {
if(skin64x64.length != 16384) {
throw new IllegalArgumentException("Skin is not 64x64!");
}
for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) {
skin64x64[(y << 8) | (x << 2)] = (byte)0xFF;
}
}
}
public static void setAlphaForChestV4(byte[] skin64x64) {
if(skin64x64.length != 12288) {
throw new IllegalArgumentException("Skin is not 64x64!");
}
for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) {
skin64x64[((y << 6) | x) * 3] |= 0x80;
}
}
}
public static SPacketOtherSkinPresetEAG makePresetResponse(EaglercraftUUID uuid) {
return new SPacketOtherSkinPresetEAG(uuid.msb, uuid.lsb, (uuid.hashCode() & 1) != 0 ? 1 : 0);
}
public static byte[] asciiString(String string) {
byte[] str = new byte[string.length()];
for(int i = 0; i < str.length; ++i) {
str[i] = (byte)string.charAt(i);
}
return str;
}
public static EaglercraftUUID createEaglerURLSkinUUID(String skinUrl) {
return EaglercraftUUID.nameUUIDFromBytes(asciiString("EaglercraftSkinURL:" + skinUrl));
}
public static int getModelId(String modelName) {
return "slim".equalsIgnoreCase(modelName) ? 1 : 0;
}
}

View File

@ -1,210 +0,0 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.Base64;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.lax1dude.eaglercraft.v1_8.sp.server.WorldsDB;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
public class IntegratedSkinService {
public static final Logger logger = LogManager.getLogger("IntegratedSkinService");
public static final byte[] skullNotFoundTexture = new byte[4096];
static {
for(int y = 0; y < 16; ++y) {
for(int x = 0; x < 64; ++x) {
int i = (y << 8) | (x << 2);
byte j = ((x + y) & 1) == 1 ? (byte)255 : 0;
skullNotFoundTexture[i] = (byte)255;
skullNotFoundTexture[i + 1] = j;
skullNotFoundTexture[i + 2] = 0;
skullNotFoundTexture[i + 3] = j;
}
}
}
public final VFile2 skullsDirectory;
public final Map<EaglercraftUUID,SkinPacketVersionCache> playerSkins = new HashMap<>();
public final Map<String,CustomSkullData> customSkulls = new HashMap<>();
private long lastFlush = 0l;
public IntegratedSkinService(VFile2 skullsDirectory) {
this.skullsDirectory = skullsDirectory;
}
public void processLoginPacket(byte[] packetData, EntityPlayerMP sender, int protocolVers) {
try {
IntegratedSkinPackets.registerEaglerPlayer(sender.getUniqueID(), packetData, this, protocolVers);
} catch (IOException e) {
logger.error("Invalid skin data packet recieved from player {}!", sender.getName());
logger.error(e);
sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin data packet recieved!");
}
}
public void processPacketGetOtherSkin(EaglercraftUUID searchUUID, EntityPlayerMP sender) {
SkinPacketVersionCache playerSkin = playerSkins.get(searchUUID);
GameMessagePacket toSend = null;
if(playerSkin != null) {
toSend = playerSkin.get(sender.playerNetServerHandler.getEaglerMessageProtocol());
}else {
toSend = IntegratedSkinPackets.makePresetResponse(searchUUID);
}
sender.playerNetServerHandler.sendEaglerMessage(toSend);
}
public void processPacketGetOtherSkin(EaglercraftUUID searchUUID, String urlStr, EntityPlayerMP sender) {
urlStr = urlStr.toLowerCase();
GameMessagePacket playerSkin;
if(!urlStr.startsWith("eagler://")) {
playerSkin = new SPacketOtherSkinPresetEAG(searchUUID.msb, searchUUID.lsb, 0);
}else {
urlStr = urlStr.substring(9);
if(urlStr.contains(VFile2.pathSeperator)) {
playerSkin = new SPacketOtherSkinPresetEAG(searchUUID.msb, searchUUID.lsb, 0);
}else {
CustomSkullData sk = customSkulls.get(urlStr);
if(sk == null) {
customSkulls.put(urlStr, sk = loadCustomSkull(urlStr));
}else {
sk.lastHit = EagRuntime.steadyTimeMillis();
}
playerSkin = sk.getSkinPacket(searchUUID, sender.playerNetServerHandler.getEaglerMessageProtocol());
}
}
sender.playerNetServerHandler.sendEaglerMessage(playerSkin);
}
public void processPacketPlayerSkin(EaglercraftUUID clientUUID, SkinPacketVersionCache generatedPacket, int skinModel) {
playerSkins.put(clientUUID, generatedPacket);
}
public void unregisterPlayer(EaglercraftUUID clientUUID) {
playerSkins.remove(clientUUID);
}
public void processPacketInstallNewSkin(byte[] skullData, EntityPlayerMP sender) {
if(!sender.canCommandSenderUseCommand(2, "give")) {
ChatComponentTranslation cc = new ChatComponentTranslation("command.skull.nopermission");
cc.getChatStyle().setColor(EnumChatFormatting.RED);
sender.addChatMessage(cc);
return;
}
String fileName = "eagler://" + installNewSkull(skullData);
NBTTagCompound rootTagCompound = new NBTTagCompound();
NBTTagCompound ownerTagCompound = new NBTTagCompound();
ownerTagCompound.setString("Name", "Eagler");
ownerTagCompound.setString("Id", EaglercraftUUID.nameUUIDFromBytes((("EaglerSkullUUID:" + fileName).getBytes(StandardCharsets.UTF_8))).toString());
NBTTagCompound propertiesTagCompound = new NBTTagCompound();
NBTTagList texturesTagList = new NBTTagList();
NBTTagCompound texturesTagCompound = new NBTTagCompound();
String texturesProp = "{\"textures\":{\"SKIN\":{\"url\":\"" + fileName + "\",\"metadata\":{\"model\":\"default\"}}}}";
texturesTagCompound.setString("Value", Base64.encodeBase64String(texturesProp.getBytes(StandardCharsets.UTF_8)));
texturesTagList.appendTag(texturesTagCompound);
propertiesTagCompound.setTag("textures", texturesTagList);
ownerTagCompound.setTag("Properties", propertiesTagCompound);
rootTagCompound.setTag("SkullOwner", ownerTagCompound);
NBTTagCompound displayTagCompound = new NBTTagCompound();
displayTagCompound.setString("Name", EnumChatFormatting.RESET + "Custom Eaglercraft Skull");
NBTTagList loreList = new NBTTagList();
loreList.appendTag(new NBTTagString(EnumChatFormatting.GRAY + (fileName.length() > 24 ? (fileName.substring(0, 22) + "...") : fileName)));
displayTagCompound.setTag("Lore", loreList);
rootTagCompound.setTag("display", displayTagCompound);
ItemStack stack = new ItemStack(Items.skull, 1, 3);
stack.setTagCompound(rootTagCompound);
boolean flag = sender.inventory.addItemStackToInventory(stack);
if (flag) {
sender.worldObj.playSoundAtEntity(sender, "random.pop", 0.2F,
((sender.getRNG().nextFloat() - sender.getRNG().nextFloat()) * 0.7F + 1.0F)
* 2.0F);
sender.inventoryContainer.detectAndSendChanges();
}
sender.addChatMessage(new ChatComponentTranslation("command.skull.feedback", fileName));
}
private static final String hex = "0123456789abcdef";
public String installNewSkull(byte[] skullData) {
// set to 16384 to save a full 64x64 skin
if(skullData.length > 4096) {
byte[] tmp = skullData;
skullData = new byte[4096];
System.arraycopy(tmp, 0, skullData, 0, 4096);
}
SHA1Digest sha = new SHA1Digest();
sha.update(skullData, 0, skullData.length);
byte[] hash = new byte[20];
sha.doFinal(hash, 0);
char[] hashText = new char[40];
for(int i = 0; i < 20; ++i) {
hashText[i << 1] = hex.charAt((hash[i] & 0xF0) >> 4);
hashText[(i << 1) + 1] = hex.charAt(hash[i] & 0x0F);
}
String str = "skin-" + new String(hashText) + ".bmp";
customSkulls.put(str, new CustomSkullData(str, skullData));
WorldsDB.newVFile(skullsDirectory, str).setAllBytes(skullData);
return str;
}
private CustomSkullData loadCustomSkull(String urlStr) {
byte[] data = WorldsDB.newVFile(skullsDirectory, urlStr).getAllBytes();
if(data == null) {
return new CustomSkullData(urlStr, skullNotFoundTexture);
}else {
return new CustomSkullData(urlStr, data);
}
}
public void flushCache() {
long cur = EagRuntime.steadyTimeMillis();
if(cur - lastFlush > 300000l) {
lastFlush = cur;
Iterator<CustomSkullData> customSkullsItr = customSkulls.values().iterator();
while(customSkullsItr.hasNext()) {
if(cur - customSkullsItr.next().lastHit > 900000l) {
customSkullsItr.remove();
}
}
}
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.sp.server.skins;
public class IntegratedTexturePackets {
public static PlayerTextureData handleTextureData(byte[] skinV1, byte[] capeV1) {
int skinId;
byte[] skinTextureDataV3;
eagler: {
if (skinV1 != null && skinV1.length > 0) {
int packetType = (int)skinV1[0] & 0xFF;
switch (packetType) {
case 0x01:
if (skinV1.length == 5) {
skinId = ((skinV1[1] & 0x7F) << 24) | ((skinV1[2] & 0xFF) << 16)
| ((skinV1[3] & 0xFF) << 8) | (skinV1[4] & 0xFF);
skinTextureDataV3 = null;
break eagler;
}
break;
case 0x02:
if (skinV1.length == 16386) {
skinId = -(Math.min((int) skinV1[1] & 0x7F, 0x7E) | 0x80) - 1;
skinTextureDataV3 = new byte[16384];
System.arraycopy(skinV1, 2, skinTextureDataV3, 0, 16384);
break eagler;
}
break;
default:
break;
}
}
skinId = 0;
skinTextureDataV3 = null;
}
int capeId;
byte[] capeTextureData;
eagler: {
if (capeV1 != null && capeV1.length > 0) {
int packetType = (int)capeV1[0] & 0xFF;
switch (packetType) {
case 0x01:
if(capeV1.length == 5) {
capeId = ((capeV1[1] & 0x7F) << 24) | ((capeV1[2] & 0xFF) << 16)
| ((capeV1[3] & 0xFF) << 8) | (capeV1[4] & 0xFF);
capeTextureData = null;
break eagler;
}
break;
case 0x02:
if (capeV1.length == 1174) {
capeId = -1;
capeTextureData = new byte[1173];
System.arraycopy(capeV1, 1, capeTextureData, 0, 1173);
break eagler;
}
break;
default:
break;
}
}
capeId = 0;
capeTextureData = null;
}
return new PlayerTextureData(skinId, skinTextureDataV3, null, capeId, capeTextureData);
}
}

View File

@ -0,0 +1,177 @@
/*
* 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.sp.server.skins;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.Base64;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherTexturesV5EAG;
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerPlayerList;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
public class IntegratedTextureService {
private final EaglerPlayerList playerList;
private final CustomSkullLoader skullHandler;
public IntegratedTextureService(EaglerPlayerList playerList, VFile2 file) {
this.playerList = playerList;
this.skullHandler = new CustomSkullLoader(file);
}
public void handleRequestPlayerSkin(EntityPlayerMP requester, EaglercraftUUID uuid) {
EntityPlayerMP target = playerList.getPlayerByUUID(uuid);
if (target != null && target.textureData != null) {
requester.playerNetServerHandler.sendEaglerMessage(target.textureData.getSkin(uuid.msb, uuid.lsb,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
} else {
requester.playerNetServerHandler.sendEaglerMessage(
new SPacketOtherSkinPresetEAG(uuid.msb, uuid.lsb, (uuid.hashCode() & 1) != 0 ? 1 : 0));
}
}
public void handleRequestPlayerCape(EntityPlayerMP requester, EaglercraftUUID uuid) {
EntityPlayerMP target = playerList.getPlayerByUUID(uuid);
if (target != null && target.textureData != null) {
requester.playerNetServerHandler.sendEaglerMessage(target.textureData.getCape(uuid.msb, uuid.lsb,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
} else {
requester.playerNetServerHandler.sendEaglerMessage(new SPacketOtherCapePresetEAG(uuid.msb, uuid.lsb, 0));
}
}
public void handleRequestPlayerSkinV5(EntityPlayerMP requester, int requestId, EaglercraftUUID uuid) {
EntityPlayerMP target = playerList.getPlayerByUUID(uuid);
if (target != null && target.textureData != null) {
requester.playerNetServerHandler.sendEaglerMessage(target.textureData.getSkinV5(requestId,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
} else {
requester.playerNetServerHandler.sendEaglerMessage(
new SPacketOtherSkinPresetV5EAG(requestId, (uuid.hashCode() & 1) != 0 ? 1 : 0));
}
}
public void handleRequestPlayerCapeV5(EntityPlayerMP requester, int requestId, EaglercraftUUID uuid) {
EntityPlayerMP target = playerList.getPlayerByUUID(uuid);
if (target != null && target.textureData != null) {
requester.playerNetServerHandler.sendEaglerMessage(target.textureData.getCapeV5(requestId,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
} else {
requester.playerNetServerHandler.sendEaglerMessage(new SPacketOtherCapePresetV5EAG(requestId, 0));
}
}
public void handleRequestPlayerTexturesV5(EntityPlayerMP requester, int requestId, EaglercraftUUID uuid) {
EntityPlayerMP target = playerList.getPlayerByUUID(uuid);
if (target != null && target.textureData != null) {
requester.playerNetServerHandler.sendEaglerMessage(target.textureData.getTexturesV5(requestId,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
} else {
requester.playerNetServerHandler
.sendEaglerMessage(new SPacketOtherTexturesV5EAG(requestId, 0, null, 0, null));
}
}
public void handleRequestSkinByURL(EntityPlayerMP requester, EaglercraftUUID uuid, String url) {
url = url.toLowerCase();
if (url.startsWith("eagler://")) {
url = url.substring(9);
if (!url.contains(VFile2.pathSeperator)) {
CustomSkullData skull = skullHandler.loadSkullData(url);
if (skull != null) {
requester.playerNetServerHandler.sendEaglerMessage(skull.getSkin(uuid.msb, uuid.lsb,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
return;
}
}
}
requester.playerNetServerHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(uuid.msb, uuid.lsb, 0));
}
public void handleRequestSkinByURLV5(EntityPlayerMP requester, int requestId, String url) {
url = url.toLowerCase();
if (url.startsWith("eagler://")) {
url = url.substring(9);
if (!url.contains(VFile2.pathSeperator)) {
CustomSkullData skull = skullHandler.loadSkullData(url);
if (skull != null) {
requester.playerNetServerHandler.sendEaglerMessage(skull.getSkinV5(requestId,
requester.playerNetServerHandler.getEaglerMessageProtocol()));
return;
}
}
}
requester.playerNetServerHandler.sendEaglerMessage(new SPacketOtherSkinPresetV5EAG(requestId, 0));
}
public void handleInstallNewSkin(EntityPlayerMP requester, byte[] skullData) {
if (!requester.canCommandSenderUseCommand(2, "give")) {
ChatComponentTranslation cc = new ChatComponentTranslation("command.skull.nopermission");
cc.getChatStyle().setColor(EnumChatFormatting.RED);
requester.addChatMessage(cc);
return;
}
String fileName = "eagler://" + skullHandler.installNewSkull(skullData);
NBTTagCompound rootTagCompound = new NBTTagCompound();
NBTTagCompound ownerTagCompound = new NBTTagCompound();
ownerTagCompound.setString("Name", "Eagler");
ownerTagCompound.setString("Id", EaglercraftUUID.nameUUIDFromBytes((("EaglerSkullUUID:" + fileName).getBytes(StandardCharsets.UTF_8))).toString());
NBTTagCompound propertiesTagCompound = new NBTTagCompound();
NBTTagList texturesTagList = new NBTTagList();
NBTTagCompound texturesTagCompound = new NBTTagCompound();
String texturesProp = "{\"textures\":{\"SKIN\":{\"url\":\"" + fileName + "\",\"metadata\":{\"model\":\"default\"}}}}";
texturesTagCompound.setString("Value", Base64.encodeBase64String(texturesProp.getBytes(StandardCharsets.UTF_8)));
texturesTagList.appendTag(texturesTagCompound);
propertiesTagCompound.setTag("textures", texturesTagList);
ownerTagCompound.setTag("Properties", propertiesTagCompound);
rootTagCompound.setTag("SkullOwner", ownerTagCompound);
NBTTagCompound displayTagCompound = new NBTTagCompound();
displayTagCompound.setString("Name", EnumChatFormatting.RESET + "Custom Eaglercraft Skull");
NBTTagList loreList = new NBTTagList();
loreList.appendTag(new NBTTagString(EnumChatFormatting.GRAY + (fileName.length() > 24 ? (fileName.substring(0, 22) + "...") : fileName)));
displayTagCompound.setTag("Lore", loreList);
rootTagCompound.setTag("display", displayTagCompound);
ItemStack stack = new ItemStack(Items.skull, 1, 3);
stack.setTagCompound(rootTagCompound);
boolean flag = requester.inventory.addItemStackToInventory(stack);
if (flag) {
requester.worldObj.playSoundAtEntity(requester, "random.pop", 0.2F,
((requester.getRNG().nextFloat() - requester.getRNG().nextFloat()) * 0.7F + 1.0F)
* 2.0F);
requester.inventoryContainer.detectAndSendChanges();
}
requester.addChatMessage(new ChatComponentTranslation("command.skull.feedback", fileName));
}
public void flushCache() {
skullHandler.flushCache();
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.sp.server.skins;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherTexturesV5EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
public class PlayerTextureData {
private int skinId;
private byte[] skinTextureDataV3;
private byte[] skinTextureDataV4;
private int capeId;
private byte[] capeTextureData;
public PlayerTextureData(int skinId, byte[] skinTextureDataV3, byte[] skinTextureDataV4, int capeId,
byte[] capeTextureData) {
this.skinId = skinId;
this.skinTextureDataV3 = skinTextureDataV3;
this.skinTextureDataV4 = skinTextureDataV4;
this.capeId = capeId;
this.capeTextureData = capeTextureData;
}
private byte[] getTextureV3() {
if (skinTextureDataV3 != null) {
return skinTextureDataV3;
} else {
return skinTextureDataV3 = SkinPacketVersionCache.convertToV3Raw(skinTextureDataV4);
}
}
private byte[] getTextureV4() {
if (skinTextureDataV4 != null) {
return skinTextureDataV4;
} else {
return skinTextureDataV4 = SkinPacketVersionCache.convertToV4Raw(skinTextureDataV3);
}
}
public GameMessagePacket getSkin(long uuidMost, long uuidLeast, GamePluginMessageProtocol protocol) {
switch(protocol) {
case V3:
if (skinId < 0) {
return new SPacketOtherSkinCustomV3EAG(uuidMost, uuidLeast, -skinId + 1, getTextureV3());
} else {
return new SPacketOtherSkinPresetEAG(uuidMost, uuidLeast, skinId);
}
case V4:
if (skinId < 0) {
return new SPacketOtherSkinCustomV4EAG(uuidMost, uuidLeast, -skinId + 1, getTextureV4());
} else {
return new SPacketOtherSkinPresetEAG(uuidMost, uuidLeast, skinId);
}
default:
throw new IllegalStateException();
}
}
public GameMessagePacket getSkinV5(int requestId, GamePluginMessageProtocol protocol) {
if(protocol.ver >= 5) {
if (skinId < 0) {
return new SPacketOtherSkinCustomV5EAG(requestId, -skinId + 1, getTextureV4());
} else {
return new SPacketOtherSkinPresetV5EAG(requestId, skinId);
}
}else {
throw new IllegalStateException();
}
}
public GameMessagePacket getCape(long uuidMost, long uuidLeast, GamePluginMessageProtocol protocol) {
if(protocol.ver <= 4) {
if (capeId < 0) {
return new SPacketOtherCapeCustomEAG(uuidMost, uuidLeast, capeTextureData);
} else {
return new SPacketOtherCapePresetEAG(uuidMost, uuidLeast, capeId);
}
}else {
throw new IllegalStateException();
}
}
public GameMessagePacket getCapeV5(int requestId, GamePluginMessageProtocol protocol) {
if(protocol.ver >= 5) {
if (capeId < 0) {
return new SPacketOtherCapeCustomV5EAG(requestId, capeTextureData);
} else {
return new SPacketOtherCapePresetV5EAG(requestId, capeId);
}
}else {
throw new IllegalStateException();
}
}
public GameMessagePacket getTexturesV5(int requestId, GamePluginMessageProtocol protocol) {
if(protocol.ver >= 5) {
return new SPacketOtherTexturesV5EAG(requestId, skinId, skinId < 0 ? getTextureV4() : null, capeId,
capeId < 0 ? capeTextureData : null);
}else {
throw new IllegalStateException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2022-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -31,6 +31,7 @@ import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.CompressionNotSupportedException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.message.InjectedMessageController;
import net.lax1dude.eaglercraft.v1_8.sp.SingleplayerServerController;
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerIntegratedServerWorker;
import net.minecraft.network.EnumConnectionState;
@ -53,6 +54,7 @@ public class IntegratedServerPlayerNetworkManager {
private int debugPacketCounter = 0;
private final List<byte[]> recievedPacketBuffer = new LinkedList<>();
private final boolean enableSendCompression;
protected InjectedMessageController injectedController = null;
private boolean firstPacket = true;
@ -70,6 +72,10 @@ public class IntegratedServerPlayerNetworkManager {
this.playerChannel = playerChannel;
this.enableSendCompression = !SingleplayerServerController.PLAYER_CHANNEL.equals(playerChannel);
}
public void setInjectedMessageController(InjectedMessageController controller) {
injectedController = controller;
}
public void connect() {
fragmentedPacket.clear();
@ -160,6 +166,10 @@ public class IntegratedServerPlayerNetworkManager {
++debugPacketCounter;
try {
if(injectedController != null && injectedController.handlePacket(fullData, 0)) {
continue;
}
ByteBuf nettyBuffer = Unpooled.buffer(fullData, fullData.length);
nettyBuffer.writerIndex(fullData.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
@ -274,6 +284,66 @@ public class IntegratedServerPlayerNetworkManager {
}
}
public void injectRawFrame(byte[] data) {
if(!isChannelOpen()) {
return;
}
if(enableSendCompression) {
int len = data.length;
if(len > compressionThreshold) {
if(compressedPacketTmp == null || compressedPacketTmp.length < len) {
compressedPacketTmp = new byte[len];
}
int cmpLen;
try {
cmpLen = EaglerZLIB.deflateFull(data, 0, len, compressedPacketTmp, 0, compressedPacketTmp.length);
}catch(IOException ex) {
logger.error("Failed to compress injected frame!");
logger.error(ex);
return;
}
byte[] compressedData = new byte[5 + cmpLen];
compressedData[0] = (byte)2;
compressedData[1] = (byte)((len >>> 24) & 0xFF);
compressedData[2] = (byte)((len >>> 16) & 0xFF);
compressedData[3] = (byte)((len >>> 8) & 0xFF);
compressedData[4] = (byte)(len & 0xFF);
System.arraycopy(compressedPacketTmp, 0, compressedData, 5, cmpLen);
if(compressedData.length > fragmentSize) {
int fragmentSizeN1 = fragmentSize - 1;
for (int j = 1; j < compressedData.length; j += fragmentSizeN1) {
byte[] fragData = new byte[((j + fragmentSizeN1 > (compressedData.length - 1)) ? ((compressedData.length - 1) % fragmentSizeN1) : fragmentSizeN1) + 1];
System.arraycopy(compressedData, j, fragData, 1, fragData.length - 1);
fragData[0] = (j + fragmentSizeN1 < compressedData.length) ? (byte) 1 : (byte) 2;
ServerPlatformSingleplayer.sendPacket(new IPCPacketData(playerChannel, fragData));
}
}else {
ServerPlatformSingleplayer.sendPacket(new IPCPacketData(playerChannel, compressedData));
}
}else {
int fragmentSizeN1 = fragmentSize - 1;
if(len > fragmentSizeN1) {
int idx = 0;
do {
int readLen = len > fragmentSizeN1 ? fragmentSizeN1 : len;
byte[] frag = new byte[readLen + 1];
System.arraycopy(data, idx, frag, 1, readLen);
idx += readLen;
len -= readLen;
frag[0] = len == 0 ? (byte)0 : (byte)1;
ServerPlatformSingleplayer.sendPacket(new IPCPacketData(playerChannel, frag));
}while(len > 0);
}else {
byte[] bytes = new byte[len + 1];
System.arraycopy(data, 0, bytes, 1, len);
ServerPlatformSingleplayer.sendPacket(new IPCPacketData(playerChannel, bytes));
}
}
}else {
ServerPlatformSingleplayer.sendPacket(new IPCPacketData(playerChannel, data));
}
}
public void setNetHandler(INetHandler nethandler) {
this.nethandler = nethandler;
}

View File

@ -0,0 +1,46 @@
/*
* 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.sp.server.socket.protocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer;
import net.minecraft.network.NetHandlerPlayServer;
public abstract class ServerMessageHandler implements GameMessageHandler {
protected final NetHandlerPlayServer netHandler;
protected final EaglerMinecraftServer server;
public ServerMessageHandler(NetHandlerPlayServer netHandler) {
this.netHandler = netHandler;
this.server = (EaglerMinecraftServer)netHandler.serverController;
}
public static ServerMessageHandler createServerHandler(int version, NetHandlerPlayServer netHandler) {
switch(version) {
case 3:
return new ServerV3MessageHandler(netHandler);
case 4:
return new ServerV4MessageHandler(netHandler);
case 5:
return new ServerV5MessageHandler(netHandler);
default:
throw new UnsupportedOperationException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-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
@ -17,58 +17,57 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer;
import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService;
import net.minecraft.network.NetHandlerPlayServer;
public class ServerV3MessageHandler implements GameMessageHandler {
private final NetHandlerPlayServer netHandler;
private final EaglerMinecraftServer server;
public class ServerV3MessageHandler extends ServerMessageHandler {
public ServerV3MessageHandler(NetHandlerPlayServer netHandler) {
this.netHandler = netHandler;
this.server = (EaglerMinecraftServer)netHandler.serverController;
super(netHandler);
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
server.getCapeService().processGetOtherCape(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
server.getTextureService().handleRequestPlayerCape(netHandler.playerEntity,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
server.getSkinService().processPacketGetOtherSkin(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
server.getTextureService().handleRequestPlayerSkin(netHandler.playerEntity,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
server.getSkinService().processPacketGetOtherSkin(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.url, netHandler.playerEntity);
server.getTextureService().handleRequestSkinByURL(netHandler.playerEntity,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.url);
}
public void handleClient(CPacketInstallSkinSPEAG packet) {
server.getSkinService().processPacketInstallNewSkin(packet.customSkin, netHandler.playerEntity);
server.getTextureService().handleInstallNewSkin(netHandler.playerEntity, packet.customSkin);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
if (voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeConnect(netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeDesc(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.desc, netHandler.playerEntity);
if (voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeDesc(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.desc, netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV3EAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
if(packet.isPeerType) {
voiceSvc.handleVoiceSignalPacketTypeDisconnectPeer(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
}else {
if (voiceSvc != null) {
if (packet.isPeerType) {
voiceSvc.handleVoiceSignalPacketTypeDisconnectPeer(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
} else {
voiceSvc.handleVoiceSignalPacketTypeDisconnect(netHandler.playerEntity);
}
}
@ -76,15 +75,17 @@ public class ServerV3MessageHandler implements GameMessageHandler {
public void handleClient(CPacketVoiceSignalICEEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeICE(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.ice, netHandler.playerEntity);
if (voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeICE(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.ice,
netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeRequest(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
if (voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeRequest(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
netHandler.playerEntity);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-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
@ -17,52 +17,21 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherPlayerClientUUIDV4EAG;
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer;
import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;
public class ServerV4MessageHandler implements GameMessageHandler {
private final NetHandlerPlayServer netHandler;
private final EaglerMinecraftServer server;
public class ServerV4MessageHandler extends ServerV3MessageHandler {
public ServerV4MessageHandler(NetHandlerPlayServer netHandler) {
this.netHandler = netHandler;
this.server = (EaglerMinecraftServer)netHandler.serverController;
super(netHandler);
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
server.getCapeService().processGetOtherCape(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
server.getSkinService().processPacketGetOtherSkin(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
server.getSkinService().processPacketGetOtherSkin(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.url, netHandler.playerEntity);
}
public void handleClient(CPacketInstallSkinSPEAG packet) {
server.getSkinService().processPacketInstallNewSkin(packet.customSkin, netHandler.playerEntity);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeConnect(netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeDesc(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.desc, netHandler.playerEntity);
}
public void handleClient(CPacketVoiceSignalDisconnectV3EAG packet) {
throw new WrongPacketException();
}
public void handleClient(CPacketVoiceSignalDisconnectV4EAG packet) {
@ -74,30 +43,19 @@ public class ServerV4MessageHandler implements GameMessageHandler {
public void handleClient(CPacketVoiceSignalDisconnectPeerV4EAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeDisconnectPeer(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeICE(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.ice, netHandler.playerEntity);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
IntegratedVoiceService voiceSvc = server.getVoiceService();
if(voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeRequest(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), netHandler.playerEntity);
if (voiceSvc != null) {
voiceSvc.handleVoiceSignalPacketTypeDisconnectPeer(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
netHandler.playerEntity);
}
}
public void handleClient(CPacketGetOtherClientUUIDV4EAG packet) {
EntityPlayerMP player = server.getConfigurationManager().getPlayerByUUID(new EaglercraftUUID(packet.playerUUIDMost, packet.playerUUIDLeast));
if(player != null && player.clientBrandUUID != null) {
netHandler.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId, player.clientBrandUUID.msb, player.clientBrandUUID.lsb));
}else {
EntityPlayerMP player = server.getConfigurationManager()
.getPlayerByUUID(new EaglercraftUUID(packet.playerUUIDMost, packet.playerUUIDLeast));
if (player != null && player.clientBrandUUID != null) {
netHandler.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
player.clientBrandUUID.msb, player.clientBrandUUID.lsb));
} else {
netHandler.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId, 0l, 0l));
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2024-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.sp.server.socket.protocol;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.minecraft.network.NetHandlerPlayServer;
public class ServerV5MessageHandler extends ServerV4MessageHandler {
public ServerV5MessageHandler(NetHandlerPlayServer netHandler) {
super(netHandler);
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
throw new WrongPacketException();
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
throw new WrongPacketException();
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
throw new WrongPacketException();
}
public void handleClient(CPacketGetOtherCapeV5EAG packet) {
server.getTextureService().handleRequestPlayerCapeV5(netHandler.playerEntity, packet.requestId,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
public void handleClient(CPacketGetOtherSkinV5EAG packet) {
server.getTextureService().handleRequestPlayerSkinV5(netHandler.playerEntity, packet.requestId,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
public void handleClient(CPacketGetSkinByURLV5EAG packet) {
server.getTextureService().handleRequestSkinByURLV5(netHandler.playerEntity, packet.requestId, packet.url);
}
public void handleClient(CPacketGetOtherTexturesV5EAG packet) {
server.getTextureService().handleRequestPlayerTexturesV5(netHandler.playerEntity, packet.requestId,
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
}

View File

@ -32,6 +32,7 @@ import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.voice.ExpiringSet;
import net.minecraft.entity.player.EntityPlayerMP;
//TODO: Rewrite to be more like EaglerXServer
public class IntegratedVoiceService {
public static final Logger logger = LogManager.getLogger("IntegratedVoiceService");
@ -151,22 +152,33 @@ public class IntegratedVoiceService {
}
GameMessagePacket v3p = null;
GameMessagePacket v4p = null;
for(EntityPlayerMP conn : voicePlayers.values()) {
if(conn.playerNetServerHandler.getEaglerMessageProtocol().ver <= 3) {
conn.playerNetServerHandler.sendEaglerMessage(v3p == null ? (v3p = new SPacketVoiceSignalConnectV3EAG(senderUuid.msb, senderUuid.lsb, true, false)) : v3p);
} else {
conn.playerNetServerHandler.sendEaglerMessage(v4p == null ? (v4p = new SPacketVoiceSignalConnectAnnounceV4EAG(senderUuid.msb, senderUuid.lsb)) : v4p);
}
}
Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(EntityPlayerMP player : voicePlayers.values()) {
EaglercraftUUID uuid = player.getUniqueID();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.msb, uuid.lsb, player.getName()));
for(EntityPlayerMP conn : voicePlayers.values()) {
EaglercraftUUID otherUuid = conn.getUniqueID();
if(conn != sender) {
if(conn.playerNetServerHandler.getEaglerMessageProtocol().ver <= 3) {
conn.playerNetServerHandler.sendEaglerMessage(v3p == null ? (v3p = new SPacketVoiceSignalConnectV3EAG(senderUuid.msb, senderUuid.lsb, true, false)) : v3p);
} else {
conn.playerNetServerHandler.sendEaglerMessage(v4p == null ? (v4p = new SPacketVoiceSignalConnectAnnounceV4EAG(senderUuid.msb, senderUuid.lsb)) : v4p);
}
}
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(otherUuid.msb, otherUuid.lsb, conn.getName()));
}
SPacketVoiceSignalGlobalEAG packetToBroadcast = new SPacketVoiceSignalGlobalEAG(userDatas);
for (EntityPlayerMP userCon : voicePlayers.values()) {
userCon.playerNetServerHandler.sendEaglerMessage(packetToBroadcast);
}
boolean selfV3 = sender.playerNetServerHandler.getEaglerMessageProtocol().ver <= 3;
for(EntityPlayerMP conn : voicePlayers.values()) {
EaglercraftUUID otherUuid = conn.getUniqueID();
if(conn != sender) {
if(selfV3) {
sender.playerNetServerHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(otherUuid.msb, otherUuid.lsb, true, false));
}else {
sender.playerNetServerHandler.sendEaglerMessage(new SPacketVoiceSignalConnectAnnounceV4EAG(otherUuid.msb, otherUuid.lsb));
}
}
}
}
public void handleVoiceSignalPacketTypeICE(EaglercraftUUID player, byte[] str, EntityPlayerMP sender) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2023-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -78,6 +78,10 @@ public class ClientIntegratedServerNetworkManager extends EaglercraftNetworkMana
byte[] next = recievedPacketBuffer.remove(0);
++debugPacketCounter;
try {
if(injectedController != null && injectedController.handlePacket(next, 0)) {
continue;
}
ByteBuf nettyBuffer = Unpooled.buffer(next, next.length);
nettyBuffer.writerIndex(next.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
@ -168,4 +172,14 @@ public class ClientIntegratedServerNetworkManager extends EaglercraftNetworkMana
public void clearRecieveQueue() {
recievedPacketBuffer.clear();
}
@Override
public void injectRawFrame(byte[] data) {
if(!isChannelOpen()) {
logger.error("Frame was injected on a closed connection");
return;
}
ClientPlatformSingleplayer.sendPacket(new IPCPacketData(address, data));
}
}

View File

@ -20,9 +20,7 @@ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.EaglercraftNetworkManager;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.client.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiDisconnected;
@ -73,15 +71,14 @@ public class NetHandlerSingleplayerLogin implements INetHandlerLoginClient {
return;
}
logger.info("Server is using protocol: {}", p);
NetHandlerPlayClient netHandler = new NetHandlerPlayClient(this.mc, this.previousGuiScreen, this.networkManager, var1.getProfile());
netHandler.setEaglerMessageController(
new GameProtocolMessageController(mp, GamePluginMessageConstants.CLIENT_TO_SERVER,
GameProtocolMessageController.createClientHandler(p, netHandler),
(ch, msg) -> netHandler.addToSendQueue(new C17PacketCustomPayload(ch, msg))));
this.networkManager.setLANInfo(p);
NetHandlerPlayClient netHandler = new NetHandlerPlayClient(this.mc, this.previousGuiScreen, this.networkManager,
var1.getProfile(), mp);
this.networkManager.setNetHandler(netHandler);
byte[] b = UpdateService.getClientSignatureData();
if(b != null) {
this.networkManager.sendPacket(new C17PacketCustomPayload("EAG|MyUpdCert-1.8", new PacketBuffer(Unpooled.buffer(b, b.length).writerIndex(b.length))));
this.networkManager.sendPacket(new C17PacketCustomPayload("EAG|MyUpdCert-1.8",
new PacketBuffer(Unpooled.buffer(b, b.length).writerIndex(b.length))));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2022-2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -18,13 +18,10 @@ package net.lax1dude.eaglercraft.v1_8.voice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
@ -32,32 +29,21 @@ import net.lax1dude.eaglercraft.v1_8.Keyboard;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformVoiceClient;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalGlobalEAG;
import net.lax1dude.eaglercraft.v1_8.sp.lan.LANServerController;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
public class VoiceClientController {
public static final String SIGNAL_CHANNEL = "EAG|Voice-1.8";
static final Logger logger = LogManager.getLogger("VoiceClientController");
private static boolean clientSupport = false;
private static boolean serverSupport = false;
private static Consumer<GameMessagePacket> packetSendCallback = null;
private static int protocolVersion = -1;
private static EnumVoiceChannelType voiceChannel = EnumVoiceChannelType.NONE;
private static final HashSet<EaglercraftUUID> nearbyPlayers = new HashSet<>();
private static final ExpiringSet<EaglercraftUUID> recentlyNearbyPlayers = new ExpiringSet<>(5000, uuid -> {
if (!nearbyPlayers.contains(uuid)) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
});
private static final Map<EaglercraftUUID, String> uuidToNameLookup = new HashMap<>(256);
private static VoiceClientInstance voiceClient = null;
static EnumVoiceChannelType lastVoiceChannel = EnumVoiceChannelType.NONE;
public static boolean isSupported() {
return isClientSupported() && isServerSupported();
@ -78,202 +64,118 @@ public class VoiceClientController {
}
public static void initializeVoiceClient(Consumer<GameMessagePacket> signalSendCallbackIn, int proto) {
if (!isClientSupported()) return;
packetSendCallback = signalSendCallbackIn;
protocolVersion = proto;
uuidToNameLookup.clear();
if (getVoiceChannel() != EnumVoiceChannelType.NONE) sendInitialVoice();
}
public static void handleVoiceSignalPacketTypeGlobal(EaglercraftUUID[] voicePlayers, String[] voiceNames) {
uuidToNameLookup.clear();
for (int i = 0; i < voicePlayers.length; i++) {
if(voiceNames != null) {
uuidToNameLookup.put(voicePlayers[i], voiceNames[i]);
}
sendPacketRequestIfNeeded(voicePlayers[i]);
handleRelease();
if (signalSendCallbackIn != null && serverSupport) {
voiceClient = new VoiceClientInstance(proto, signalSendCallbackIn);
voiceClient.initialize(lastVoiceChannel);
}
}
private static void handleRelease() {
if (voiceClient != null) {
voiceClient.release();
voiceClient = null;
}
speakingSet.clear();
activateVoice(false);
}
public static void handleVoiceSignalPacketTypeGlobalNew(Collection<SPacketVoiceSignalGlobalEAG.UserData> voicePlayers) {
boolean isGlobal = voiceChannel == EnumVoiceChannelType.GLOBAL;
uuidToNameLookup.clear();
for (SPacketVoiceSignalGlobalEAG.UserData player : voicePlayers) {
EaglercraftUUID uuid = new EaglercraftUUID(player.uuidMost, player.uuidLeast);
if(player.username != null) {
uuidToNameLookup.put(uuid, player.username);
}
if (isGlobal) {
sendPacketRequestIfNeeded(uuid);
}
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeGlobal(voicePlayers);
}
}
public static void handleServerDisconnect() {
if(!isClientSupported()) return;
if (!isClientSupported()) return;
serverSupport = false;
uuidToNameLookup.clear();
for (EaglercraftUUID uuid : nearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
for (EaglercraftUUID uuid : recentlyNearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
nearbyPlayers.clear();
recentlyNearbyPlayers.clear();
Set<EaglercraftUUID> antiConcurrentModificationUUIDs = new HashSet<>(listeningSet);
for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
activateVoice(false);
packetSendCallback = null;
protocolVersion = -1;
handleRelease();
}
public static void handleVoiceSignalPacketTypeAllowed(boolean voiceAvailableStat, String[] servs) {
serverSupport = voiceAvailableStat;
PlatformVoiceClient.setICEServers(servs);
if(isSupported()) {
EnumVoiceChannelType ch = getVoiceChannel();
setVoiceChannel(EnumVoiceChannelType.NONE);
setVoiceChannel(ch);
if(packetSendCallback != null) {
handleRelease();
PlatformVoiceClient.setICEServers(servs);
if (serverSupport != voiceAvailableStat) {
serverSupport = voiceAvailableStat;
if (voiceAvailableStat) {
voiceClient = new VoiceClientInstance(protocolVersion, packetSendCallback);
voiceClient.initialize(lastVoiceChannel);
}
}
}
}
public static void handleVoiceSignalPacketTypeConnect(EaglercraftUUID user, boolean offer) {
if (voiceChannel != EnumVoiceChannelType.NONE) PlatformVoiceClient.signalConnect(user, offer);
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeConnect(user, offer);
}
}
public static void handleVoiceSignalPacketTypeConnectAnnounce(EaglercraftUUID user) {
if (voiceChannel != EnumVoiceChannelType.NONE && (voiceChannel == EnumVoiceChannelType.GLOBAL || listeningSet.contains(user))) sendPacketRequest(user);
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeConnectAnnounce(user);
}
}
public static void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID user) {
if (voiceChannel != EnumVoiceChannelType.NONE) PlatformVoiceClient.signalDisconnect(user, true);
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeDisconnect(user);
}
}
public static void handleVoiceSignalPacketTypeICECandidate(EaglercraftUUID user, String ice) {
if (voiceChannel != EnumVoiceChannelType.NONE) PlatformVoiceClient.signalICECandidate(user, ice);
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeICECandidate(user, ice);
}
}
public static void handleVoiceSignalPacketTypeDescription(EaglercraftUUID user, String desc) {
if (voiceChannel != EnumVoiceChannelType.NONE) PlatformVoiceClient.signalDescription(user, desc);
if (voiceClient != null) {
voiceClient.handleVoiceSignalPacketTypeDescription(user, desc);
}
}
public static void tickVoiceClient(Minecraft mc) {
if(!isClientSupported()) return;
recentlyNearbyPlayers.checkForExpirations();
speakingSet.clear();
PlatformVoiceClient.tickVoiceClient();
if (getVoiceChannel() != EnumVoiceChannelType.NONE && (getVoiceStatus() == EnumVoiceChannelStatus.CONNECTING || getVoiceStatus() == EnumVoiceChannelStatus.CONNECTED)) {
activateVoice((mc.currentScreen == null || !mc.currentScreen.blockPTTKey()) && Keyboard.isKeyDown(mc.gameSettings.voicePTTKey));
if(mc.isSingleplayer() && !LANServerController.isHostingLAN()) {
setVoiceChannel(EnumVoiceChannelType.NONE);
return;
}
if (mc.theWorld != null && mc.thePlayer != null) {
HashSet<EaglercraftUUID> seenPlayers = new HashSet<>();
for (EntityPlayer player : mc.theWorld.playerEntities) {
if (player == mc.thePlayer) continue;
if (getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) updateVoicePosition(player.getUniqueID(), player.posX, player.posY + player.getEyeHeight(), player.posZ);
int prox = 22;
// cube
if (Math.abs(mc.thePlayer.posX - player.posX) <= prox && Math.abs(mc.thePlayer.posY - player.posY) <= prox && Math.abs(mc.thePlayer.posZ - player.posZ) <= prox) {
if (!uuidToNameLookup.containsKey(player.getUniqueID())) {
uuidToNameLookup.put(player.getUniqueID(), player.getName());
}
if (addNearbyPlayer(player.getUniqueID())) {
seenPlayers.add(player.getUniqueID());
}
}
}
cleanupNearbyPlayers(seenPlayers);
public static void tickVoiceClient() {
if (voiceClient != null) {
voiceClient.tickVoiceClient();
Minecraft mc = Minecraft.getMinecraft();
if (voiceClient.getVoiceChannel() != EnumVoiceChannelType.NONE) {
activateVoice((mc.currentScreen == null || !mc.currentScreen.blockPTTKey())
&& Keyboard.isKeyDown(mc.gameSettings.voicePTTKey));
} else {
activateVoice(false);
}
speakingSet.clear();
PlatformVoiceClient.tickVoiceClient();
}
}
public static final boolean addNearbyPlayer(EaglercraftUUID uuid) {
recentlyNearbyPlayers.remove(uuid);
if (nearbyPlayers.add(uuid)) {
sendPacketRequestIfNeeded(uuid);
return true;
}
return false;
}
public static final void removeNearbyPlayer(EaglercraftUUID uuid) {
if (nearbyPlayers.remove(uuid)) {
if (getVoiceStatus() == EnumVoiceChannelStatus.DISCONNECTED || getVoiceStatus() == EnumVoiceChannelStatus.UNAVAILABLE) return;
if (voiceChannel == EnumVoiceChannelType.PROXIMITY) recentlyNearbyPlayers.add(uuid);
}
}
public static final void cleanupNearbyPlayers(HashSet<EaglercraftUUID> existingPlayers) {
nearbyPlayers.stream().filter(ud -> !existingPlayers.contains(ud)).collect(Collectors.toSet()).forEach(VoiceClientController::removeNearbyPlayer);
}
public static final void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) {
PlatformVoiceClient.updateVoicePosition(uuid, x, y, z);
}
public static void setVoiceChannel(EnumVoiceChannelType channel) {
if (voiceChannel == channel) return;
if (channel != EnumVoiceChannelType.NONE) PlatformVoiceClient.initializeDevices();
if (channel == EnumVoiceChannelType.NONE) {
for (EaglercraftUUID uuid : nearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
for (EaglercraftUUID uuid : recentlyNearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
nearbyPlayers.clear();
recentlyNearbyPlayers.clear();
Set<EaglercraftUUID> antiConcurrentModificationUUIDs = new HashSet<>(listeningSet);
for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
sendPacketDisconnect();
activateVoice(false);
} else if (voiceChannel == EnumVoiceChannelType.PROXIMITY) {
for (EaglercraftUUID uuid : nearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
for (EaglercraftUUID uuid : recentlyNearbyPlayers) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
nearbyPlayers.clear();
recentlyNearbyPlayers.clear();
sendPacketDisconnect();
} else if(voiceChannel == EnumVoiceChannelType.GLOBAL) {
Set<EaglercraftUUID> antiConcurrentModificationUUIDs = new HashSet<>(listeningSet);
antiConcurrentModificationUUIDs.removeAll(nearbyPlayers);
antiConcurrentModificationUUIDs.removeAll(recentlyNearbyPlayers);
for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) {
PlatformVoiceClient.signalDisconnect(uuid, false);
}
sendPacketDisconnect();
}
voiceChannel = channel;
if (channel != EnumVoiceChannelType.NONE) {
sendInitialVoice();
}
}
public static void sendInitialVoice() {
sendPacketConnect();
for (EaglercraftUUID uuid : nearbyPlayers) {
sendPacketRequest(uuid);
if (voiceClient != null) {
voiceClient.setVoiceChannel(channel);
}
}
public static EnumVoiceChannelType getVoiceChannel() {
return voiceChannel;
if (voiceClient != null) {
return voiceClient.getVoiceChannel();
} else {
return EnumVoiceChannelType.NONE;
}
}
public static EnumVoiceChannelStatus getVoiceStatus() {
return (!isClientSupported() || !isServerSupported()) ? EnumVoiceChannelStatus.UNAVAILABLE :
(PlatformVoiceClient.getReadyState() != EnumVoiceChannelReadyState.DEVICE_INITIALIZED ?
EnumVoiceChannelStatus.CONNECTING : EnumVoiceChannelStatus.CONNECTED);
if (voiceClient != null) {
return voiceClient.getVoiceStatus();
} else {
return EnumVoiceChannelStatus.UNAVAILABLE;
}
}
private static boolean talkStatus = false;
@ -350,60 +252,29 @@ public class VoiceClientController {
}
public static String getVoiceUsername(EaglercraftUUID uuid) {
if(uuid == null) {
return "null";
if (voiceClient != null) {
return voiceClient.getVoiceUsername(uuid);
} else {
return uuid.toString();
}
String ret = uuidToNameLookup.get(uuid);
return ret == null ? uuid.toString() : ret;
}
public static void sendPacketICE(EaglercraftUUID peerId, String candidate) {
if(packetSendCallback != null) {
packetSendCallback.accept(new CPacketVoiceSignalICEEAG(peerId.msb, peerId.lsb, candidate));
if (voiceClient != null) {
voiceClient.sendPacketICE(peerId, candidate);
}
}
public static void sendPacketDesc(EaglercraftUUID peerId, String desc) {
if(packetSendCallback != null) {
packetSendCallback.accept(new CPacketVoiceSignalDescEAG(peerId.msb, peerId.lsb, desc));
}
}
public static void sendPacketDisconnect() {
if(packetSendCallback != null) {
if(protocolVersion <= 3) {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectV3EAG());
}else {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectV4EAG());
}
if (voiceClient != null) {
voiceClient.sendPacketDesc(peerId, desc);
}
}
public static void sendPacketDisconnectPeer(EaglercraftUUID peerId) {
if(packetSendCallback != null) {
if(protocolVersion <= 3) {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectV3EAG(true, peerId.msb, peerId.lsb));
}else {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectPeerV4EAG(peerId.msb, peerId.lsb));
}
if (voiceClient != null) {
voiceClient.sendPacketDisconnectPeer(peerId);
}
}
public static void sendPacketConnect() {
if(packetSendCallback != null) {
packetSendCallback.accept(new CPacketVoiceSignalConnectEAG());
}
}
public static void sendPacketRequest(EaglercraftUUID peerId) {
if(packetSendCallback != null) {
packetSendCallback.accept(new CPacketVoiceSignalRequestEAG(peerId.msb, peerId.lsb));
}
}
private static void sendPacketRequestIfNeeded(EaglercraftUUID uuid) {
if (getVoiceStatus() == EnumVoiceChannelStatus.DISCONNECTED || getVoiceStatus() == EnumVoiceChannelStatus.UNAVAILABLE) return;
if(uuid.equals(EaglerProfile.getPlayerUUID())) return;
if (!getVoiceListening().contains(uuid)) sendPacketRequest(uuid);
}
}

View File

@ -0,0 +1,276 @@
/*
* Copyright (c) 2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.voice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformVoiceClient;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalConnectEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalDescEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalDisconnectPeerV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalDisconnectV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalDisconnectV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalICEEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketVoiceSignalRequestEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalGlobalEAG;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
public class VoiceClientInstance {
private final int protocolVers;
private final Consumer<GameMessagePacket> packetSendCallback;
private final Map<EaglercraftUUID, VoicePlayerState> voicePlayers = new HashMap<>(32);
private EnumVoiceChannelType voiceChannel = EnumVoiceChannelType.NONE;
private long lastUpdate = 0l;
protected VoiceClientInstance(int protocolVers, Consumer<GameMessagePacket> packetSendCallback) {
this.protocolVers = protocolVers;
this.packetSendCallback = packetSendCallback;
}
public void initialize(EnumVoiceChannelType voiceChannel) {
setVoiceChannel(voiceChannel);
}
public EnumVoiceChannelType getVoiceChannel() {
return voiceChannel;
}
public void setVoiceChannel(EnumVoiceChannelType channel) {
VoiceClientController.lastVoiceChannel = channel;
if (voiceChannel == channel) return;
if (channel != EnumVoiceChannelType.NONE) {
PlatformVoiceClient.initializeDevices();
}
if (channel == EnumVoiceChannelType.NONE) {
release();
packetSendCallback.accept(new CPacketVoiceSignalDisconnectV4EAG());
} else {
if (voiceChannel == EnumVoiceChannelType.PROXIMITY && channel == EnumVoiceChannelType.GLOBAL) {
for (VoicePlayerState state : voicePlayers.values()) {
if(!state.nearby) {
state.tryRequest(EagRuntime.steadyTimeMillis());
}
PlatformVoiceClient.makePeerGlobal(state.uuid);
}
} else if (voiceChannel == EnumVoiceChannelType.GLOBAL && channel == EnumVoiceChannelType.PROXIMITY) {
for (VoicePlayerState state : new ArrayList<>(voicePlayers.values())) {
recheckNearby(state);
if(!state.nearby) {
PlatformVoiceClient.signalDisconnect(state.uuid, false);
}
PlatformVoiceClient.makePeerProximity(state.uuid);
}
} else if (voiceChannel == EnumVoiceChannelType.NONE) {
packetSendCallback.accept(new CPacketVoiceSignalConnectEAG());
}
}
voiceChannel = channel;
}
public void handleVoiceSignalPacketTypeGlobal(Collection<SPacketVoiceSignalGlobalEAG.UserData> voicePlayers) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
EaglercraftUUID self = Minecraft.getMinecraft().getSession().getProfile().getId();
Set<EaglercraftUUID> playersLost = new HashSet<>(this.voicePlayers.keySet());
for (SPacketVoiceSignalGlobalEAG.UserData userData : voicePlayers) {
EaglercraftUUID uuid = new EaglercraftUUID(userData.uuidMost, userData.uuidLeast);
if (!uuid.equals(self)) {
if (!playersLost.remove(uuid)) {
announcePlayer(uuid);
}
this.voicePlayers.get(uuid).name = userData.username;
}
}
if (!playersLost.isEmpty()) {
for (EaglercraftUUID uuid : playersLost) {
dropPlayer(uuid);
}
}
}
}
private void announcePlayer(EaglercraftUUID uuid) {
VoicePlayerState voiceState = new VoicePlayerState(this, uuid);
voicePlayers.put(uuid, voiceState);
if (voiceChannel == EnumVoiceChannelType.GLOBAL) {
voiceState.tryRequest(EagRuntime.steadyTimeMillis());
} else if (voiceChannel == EnumVoiceChannelType.PROXIMITY) {
recheckNearby(voiceState);
if(voiceState.nearby) {
voiceState.tryRequest(EagRuntime.steadyTimeMillis());
}
}
}
private void recheckNearby(VoicePlayerState voiceState) {
Minecraft mc = Minecraft.getMinecraft();
if(mc.theWorld != null) {
EntityPlayer player = mc.theWorld.getPlayerEntityByUUID(voiceState.uuid);
if(player != null) {
voiceState.nearby = goddamnManhattanDistance(mc, player);
if(voiceState.nearby) {
PlatformVoiceClient.updateVoicePosition(voiceState.uuid, player.posX, player.posY, player.posZ);
}
return;
}
}
voiceState.nearby = false;
}
// Must perform these ham-fisted manhattan distance calculations to work with old clients
private boolean goddamnManhattanDistance(Minecraft mc, Entity player) {
final int prox = 22;
return Math.abs(mc.thePlayer.posX - player.posX) <= prox && Math.abs(mc.thePlayer.posY - player.posY) <= prox
&& Math.abs(mc.thePlayer.posZ - player.posZ) <= prox;
}
private void dropPlayer(EaglercraftUUID uuid) {
voicePlayers.remove(uuid);
PlatformVoiceClient.signalDisconnect(uuid, true);
}
public void handleVoiceSignalPacketTypeConnectAnnounce(EaglercraftUUID user) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
if (!voicePlayers.containsKey(user)) {
// Backwards compat with old servers :(
announcePlayer(user);
}
}
}
public void handleVoiceSignalPacketTypeConnect(EaglercraftUUID user, boolean offer) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
PlatformVoiceClient.signalConnect(user, offer);
}
}
public void handleVoiceSignalPacketTypeICECandidate(EaglercraftUUID user, String ice) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
PlatformVoiceClient.signalICECandidate(user, ice);
}
}
public void handleVoiceSignalPacketTypeDescription(EaglercraftUUID user, String desc) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
PlatformVoiceClient.signalDescription(user, desc);
}
}
public void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID user) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
VoicePlayerState state = voicePlayers.get(user);
if (state != null) {
state.handleDisconnect();
}
PlatformVoiceClient.signalDisconnect(user, true);
}
}
public void sendPacketRequest(VoicePlayerState voicePlayerState) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
packetSendCallback.accept(new CPacketVoiceSignalRequestEAG(voicePlayerState.uuid.msb, voicePlayerState.uuid.lsb));
}
}
public void sendPacketDesc(EaglercraftUUID peerId, String desc) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
packetSendCallback.accept(new CPacketVoiceSignalDescEAG(peerId.msb, peerId.lsb, desc));
}
}
public void sendPacketICE(EaglercraftUUID peerId, String candidate) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
packetSendCallback.accept(new CPacketVoiceSignalICEEAG(peerId.msb, peerId.lsb, candidate));
}
}
public void sendPacketDisconnectPeer(EaglercraftUUID peerId) {
if (voiceChannel != EnumVoiceChannelType.NONE) {
VoicePlayerState state = voicePlayers.get(peerId);
if (state != null) {
state.handleDisconnect();
}
if (protocolVers >= 4) {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectPeerV4EAG(peerId.msb, peerId.lsb));
} else {
packetSendCallback.accept(new CPacketVoiceSignalDisconnectV3EAG(true, peerId.msb, peerId.lsb));
}
}
}
public String getVoiceUsername(EaglercraftUUID uuid) {
VoicePlayerState state = voicePlayers.get(uuid);
if (state != null) {
return state.name;
} else {
return uuid.toString();
}
}
public EnumVoiceChannelStatus getVoiceStatus() {
if (voiceChannel != EnumVoiceChannelType.NONE) {
return PlatformVoiceClient.getReadyState() != EnumVoiceChannelReadyState.DEVICE_INITIALIZED ?
EnumVoiceChannelStatus.CONNECTING : EnumVoiceChannelStatus.CONNECTED;
} else {
return EnumVoiceChannelStatus.DISCONNECTED;
}
}
public void tickVoiceClient() {
if (voiceChannel == EnumVoiceChannelType.GLOBAL) {
long millis = EagRuntime.steadyTimeMillis();
if (millis - lastUpdate > 1500l) {
lastUpdate = millis;
for (VoicePlayerState state : voicePlayers.values()) {
state.tryRequest(millis);
}
}
} else if (voiceChannel == EnumVoiceChannelType.PROXIMITY) {
long millis = EagRuntime.steadyTimeMillis();
if (millis - lastUpdate > 100l) {
lastUpdate = millis;
for (VoicePlayerState state : voicePlayers.values()) {
boolean old = state.nearby;
recheckNearby(state);
if (state.nearby) {
state.tryRequest(millis);
} else if (old) {
PlatformVoiceClient.signalDisconnect(state.uuid, false);
}
}
}
}
}
public void release() {
for (VoicePlayerState state : new ArrayList<>(voicePlayers.values())) {
dropPlayer(state.uuid);
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2025 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.voice;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
public class VoicePlayerState {
public final VoiceClientInstance client;
public final EaglercraftUUID uuid;
public long lastRequest = -15000l;
public String name = null;
public boolean nearby = false;
public VoicePlayerState(VoiceClientInstance client, EaglercraftUUID uuid) {
this.client = client;
this.uuid = uuid;
}
public void tryRequest(long millis) {
if (!VoiceClientController.getVoiceListening().contains(uuid)) {
if (millis - lastRequest > 4000l) {
lastRequest = millis;
client.sendPacketRequest(this);
}
}
}
public void handleDisconnect() {
lastRequest = 0l;
}
public boolean isListening() {
return VoiceClientController.getVoiceListening().contains(uuid);
}
}

View File

@ -40,17 +40,17 @@ public class GuiScreenPhishingWarning extends GuiScreen {
public void initGui() {
this.buttonList.clear();
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 134, I18n.format("webviewPhishingWaring.continue")));
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 134, I18n.format("webviewPhishingWarning.continue")));
}
public void drawScreen(int mx, int my, float pt) {
this.drawDefaultBackground();
this.drawCenteredString(fontRendererObj, EnumChatFormatting.BOLD + I18n.format("webviewPhishingWaring.title"), this.width / 2, 70, 0xFF4444);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWaring.text0"), this.width / 2, 90, 16777215);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWaring.text1"), this.width / 2, 102, 16777215);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWaring.text2"), this.width / 2, 114, 16777215);
this.drawCenteredString(fontRendererObj, EnumChatFormatting.BOLD + I18n.format("webviewPhishingWarning.title"), this.width / 2, 70, 0xFF4444);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWarning.text0"), this.width / 2, 90, 16777215);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWarning.text1"), this.width / 2, 102, 16777215);
this.drawCenteredString(fontRendererObj, I18n.format("webviewPhishingWarning.text2"), this.width / 2, 114, 16777215);
String dontShowAgain = I18n.format("webviewPhishingWaring.dontShowAgain");
String dontShowAgain = I18n.format("webviewPhishingWarning.dontShowAgain");
int w = fontRendererObj.getStringWidth(dontShowAgain) + 20;
int ww = (this.width - w) / 2;
this.drawString(fontRendererObj, dontShowAgain, ww + 20, 137, 0xCCCCCC);

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.EaglerZLIB;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.IOUtils;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.internal.WebViewOptions;
@ -34,6 +35,7 @@ import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketRequestServerInfoV4EAG;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.I18n;
@ -44,13 +46,25 @@ public class GuiScreenRecieveServerInfo extends GuiScreen {
protected final GuiScreen parent;
protected final byte[] expectHash;
protected final IDisplayWebviewProc proc;
protected int timer;
protected int timer2;
protected String statusString = "recieveServerInfo.checkingCache";
public static interface IDisplayWebviewProc {
GuiScreen display(GuiScreen parent, byte[] blob, EaglercraftUUID permissionsOriginUUID);
}
public GuiScreenRecieveServerInfo(GuiScreen parent, byte[] expectHash) {
this.parent = parent;
this.expectHash = expectHash;
this.proc = GuiScreenServerInfo::createForCurrentState;
}
public GuiScreenRecieveServerInfo(GuiScreen parent, byte[] expectHash, IDisplayWebviewProc proc) {
this.parent = parent;
this.expectHash = expectHash;
this.proc = proc;
}
public void initGui() {
@ -105,28 +119,26 @@ public class GuiScreenRecieveServerInfo extends GuiScreen {
mc.displayGuiScreen(parent);
return;
}
NetHandlerPlayClient sendQueue = mc.thePlayer.sendQueue;
++timer;
if(timer == 1) {
byte[] data = ServerInfoCache.loadFromCache(expectHash);
if(data != null) {
mc.displayGuiScreen(GuiScreenServerInfo.createForCurrentState(parent, data, WebViewOptions.getEmbedOriginUUID(expectHash)));
mc.displayGuiScreen(proc.display(parent, data, WebViewOptions.getEmbedOriginUUID(expectHash)));
}else {
byte[] b = mc.thePlayer.sendQueue.cachedServerInfoData;
if(b != null) {
byte[] b = sendQueue.cachedServerInfoData;
if(b != null && Arrays.equals(expectHash, sendQueue.cachedServerInfoHash)) {
if(b.length == 0) {
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
}else {
ServerInfoCache.storeInCache(expectHash, b);
mc.displayGuiScreen(GuiScreenServerInfo.createForCurrentState(parent, b, WebViewOptions.getEmbedOriginUUID(expectHash)));
mc.displayGuiScreen(proc.display(parent, b, WebViewOptions.getEmbedOriginUUID(expectHash)));
}
}else {
statusString = "recieveServerInfo.contactingServer";
if(!mc.thePlayer.sendQueue.hasRequestedServerInfo) {
if(!ServerInfoCache.hasLastChunk || !Arrays.equals(ServerInfoCache.chunkRecieveHash, expectHash)) {
ServerInfoCache.clearDownload();
mc.thePlayer.sendQueue.sendEaglerMessage(new CPacketRequestServerInfoV4EAG(expectHash));
mc.thePlayer.sendQueue.hasRequestedServerInfo = true;
}
if(!ServerInfoCache.hasLastChunk || !Arrays.equals(ServerInfoCache.chunkRecieveHash, expectHash)) {
ServerInfoCache.clearDownload();
sendQueue.sendEaglerMessage(new CPacketRequestServerInfoV4EAG(expectHash));
}
}
}
@ -144,7 +156,8 @@ public class GuiScreenRecieveServerInfo extends GuiScreen {
}
if(i != ServerInfoCache.chunkCurrentSize) {
logger.error("An unknown error occured!");
mc.thePlayer.sendQueue.cachedServerInfoData = new byte[0];
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = new byte[0];
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
return;
}
@ -154,14 +167,16 @@ public class GuiScreenRecieveServerInfo extends GuiScreen {
int finalSize = (new DataInputStream(bis)).readInt();
if(finalSize < 0) {
logger.error("The response data was corrupt, decompressed size is negative!");
mc.thePlayer.sendQueue.cachedServerInfoData = new byte[0];
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = new byte[0];
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
return;
}
if(finalSize > ServerInfoCache.CACHE_MAX_SIZE * 2) {
logger.error("Failed to decompress/verify server info response! Size is massive, {} " + finalSize + " bytes reported!");
logger.error("Failed to decompress/verify server info response! Size is massive, {} bytes reported!", finalSize);
logger.error("Aborting decompression. Rejoin the server to try again.");
mc.thePlayer.sendQueue.cachedServerInfoData = new byte[0];
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = new byte[0];
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
return;
}
@ -175,17 +190,20 @@ public class GuiScreenRecieveServerInfo extends GuiScreen {
digest.doFinal(csum, 0);
if(Arrays.equals(csum, expectHash)) {
ServerInfoCache.storeInCache(csum, decompressed);
mc.thePlayer.sendQueue.cachedServerInfoData = decompressed;
mc.displayGuiScreen(GuiScreenServerInfo.createForCurrentState(parent, decompressed, WebViewOptions.getEmbedOriginUUID(expectHash)));
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = decompressed;
mc.displayGuiScreen(proc.display(parent, decompressed, WebViewOptions.getEmbedOriginUUID(expectHash)));
}else {
logger.error("The data recieved from the server did not have the correct SHA1 checksum! Rejoin the server to try again.");
mc.thePlayer.sendQueue.cachedServerInfoData = new byte[0];
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = new byte[0];
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
}
}catch(IOException ex) {
logger.error("Failed to decompress/verify server info response! Rejoin the server to try again.");
logger.error(ex);
mc.thePlayer.sendQueue.cachedServerInfoData = new byte[0];
sendQueue.cachedServerInfoHash = expectHash;
sendQueue.cachedServerInfoData = new byte[0];
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("serverInfoFailure.title", "serverInfoFailure.desc", parent));
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.
*
*/
package net.lax1dude.eaglercraft.v1_8.webview;
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
public class GuiScreenRequestDisplay extends GuiScreen {
private static final ResourceLocation beaconGuiTexture = new ResourceLocation("textures/gui/container/beacon.png");
private GuiScreen cont;
private GuiScreen back;
private NetHandlerPlayClient netHandler;
private boolean mouseOverCheck;
private boolean hasCheckedBox;
public GuiScreenRequestDisplay(GuiScreen cont, GuiScreen back, NetHandlerPlayClient netHandler) {
this.cont = cont;
this.back = back;
this.netHandler = netHandler;
}
public void initGui() {
this.buttonList.clear();
this.buttonList.add(new GuiButton(0, this.width / 2 + 2, this.height / 6 + 122, 148, 20, I18n.format("webviewPhishingWarning.continue")));
this.buttonList.add(new GuiButton(1, this.width / 2 - 150, this.height / 6 + 122, 148, 20, I18n.format("gui.cancel")));
}
public void drawScreen(int mx, int my, float pt) {
this.drawDefaultBackground();
this.drawCenteredString(fontRendererObj, EnumChatFormatting.BOLD + I18n.format("webviewDisplayWarning.title"), this.width / 2, 70, 0xFF4444);
this.drawCenteredString(fontRendererObj, I18n.format("webviewDisplayWarning.text0"), this.width / 2, 90, 16777215);
this.drawCenteredString(fontRendererObj, I18n.format("webviewDisplayWarning.text1"), this.width / 2, 102, 16777215);
String dontShowAgain = I18n.format("webviewPhishingWarning.dontShowAgain");
int w = fontRendererObj.getStringWidth(dontShowAgain) + 20;
int ww = (this.width - w) / 2;
this.drawString(fontRendererObj, dontShowAgain, ww + 20, 125, 0xCCCCCC);
mouseOverCheck = ww < mx && ww + 17 > mx && 121 < my && 138 > my;
if(mouseOverCheck) {
GlStateManager.color(0.7f, 0.7f, 1.0f, 1.0f);
}else {
GlStateManager.color(0.6f, 0.6f, 0.6f, 1.0f);
}
mc.getTextureManager().bindTexture(beaconGuiTexture);
GlStateManager.pushMatrix();
GlStateManager.scale(0.75f, 0.75f, 0.75f);
drawTexturedModalRect(ww * 4 / 3, 121 * 4 / 3, 22, 219, 22, 22);
GlStateManager.popMatrix();
if(hasCheckedBox) {
GlStateManager.pushMatrix();
GlStateManager.color(1.1f, 1.1f, 1.1f, 1.0f);
GlStateManager.translate(0.5f, 0.5f, 0.0f);
drawTexturedModalRect(ww, 121, 90, 222, 16, 16);
GlStateManager.popMatrix();
}
super.drawScreen(mx, my, pt);
}
protected void actionPerformed(GuiButton par1GuiButton) {
if(par1GuiButton.id == 0) {
if(hasCheckedBox) {
netHandler.allowedDisplayWebview = true;
netHandler.allowedDisplayWebviewYes = true;
}
mc.displayGuiScreen(cont);
}else if (par1GuiButton.id == 1) {
if(hasCheckedBox) {
netHandler.allowedDisplayWebview = true;
netHandler.allowedDisplayWebviewYes = false;
}
mc.displayGuiScreen(back);
}
}
@Override
protected void mouseClicked(int mx, int my, int btn) {
if(btn == 0 && mouseOverCheck) {
hasCheckedBox = !hasCheckedBox;
mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F));
return;
}
super.mouseClicked(mx, my, btn);
}
}

View File

@ -26,6 +26,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.WebViewOptions;
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.protocol.pkt.server.SPacketDisplayWebViewURLV5EAG;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
@ -91,6 +92,54 @@ public class GuiScreenServerInfo extends GuiScreen {
opts.fallbackTitle = PauseMenuCustomizeState.serverInfoEmbedTitle;
}
public static GuiScreen createForDisplayRequest(GuiScreen parent, int perms, String title, String url) {
URI urlObj;
try {
urlObj = new URI(url);
}catch(URISyntaxException ex) {
logger.error("Refusing to iframe an invalid URL: {}", url);
logger.error(ex);
return new GuiScreenGenericErrorMessage("webviewInvalidURL.title", "webviewInvalidURL.desc", parent);
}
return createForDisplayRequest(parent, perms, title, urlObj);
}
public static GuiScreen createForDisplayRequest(GuiScreen parent, int perms, String title, URI url) {
boolean support = WebViewOverlayController.supported();
boolean fallbackSupport = WebViewOverlayController.fallbackSupported();
if(!support && !fallbackSupport) {
return new GuiScreenGenericErrorMessage("webviewNotSupported.title", "webviewNotSupported.desc", parent);
}
WebViewOptions opts = new WebViewOptions();
opts.contentMode = EnumWebViewContentMode.URL_BASED;
opts.url = url;
setupState(opts, perms, title);
opts.permissionsOriginUUID = WebViewOptions.getURLOriginUUID(url);
return support ? new GuiScreenServerInfo(parent, opts) : new GuiScreenServerInfoDesktop(parent, opts);
}
public static GuiScreen createForDisplayRequest(GuiScreen parent, int perms, String title, byte[] blob,
EaglercraftUUID permissionsOriginUUID) {
boolean support = WebViewOverlayController.supported();
boolean fallbackSupport = WebViewOverlayController.fallbackSupported();
if(!support && !fallbackSupport) {
return new GuiScreenGenericErrorMessage("webviewNotSupported.title", "webviewNotSupported.desc", parent);
}
WebViewOptions opts = new WebViewOptions();
opts.contentMode = EnumWebViewContentMode.BLOB_BASED;
opts.blob = blob;
setupState(opts, perms, title);
opts.permissionsOriginUUID = permissionsOriginUUID;
return support ? new GuiScreenServerInfo(parent, opts) : new GuiScreenServerInfoDesktop(parent, opts);
}
public static void setupState(WebViewOptions opts, int perms, String title) {
opts.scriptEnabled = (perms & SPacketDisplayWebViewURLV5EAG.FLAG_PERMS_JAVASCRIPT) != 0;
opts.strictCSPEnable = (perms & SPacketDisplayWebViewURLV5EAG.FLAG_PERMS_STRICT_CSP) != 0;
opts.serverMessageAPIEnabled = (perms & SPacketDisplayWebViewURLV5EAG.FLAG_PERMS_MESSAGE_API) != 0;
opts.fallbackTitle = title;
}
public void initGui() {
ScaledResolution res = mc.scaledResolution;
if(!isShowing) {
@ -118,8 +167,7 @@ public class GuiScreenServerInfo extends GuiScreen {
public void drawScreen(int mx, int my, float pt) {
drawDefaultBackground();
drawCenteredString(fontRendererObj, PauseMenuCustomizeState.serverInfoEmbedTitle == null ? "Server Info"
: PauseMenuCustomizeState.serverInfoEmbedTitle, width / 2, 13, 0xFFFFFF);
drawCenteredString(fontRendererObj, opts.fallbackTitle == null ? "Server Info" : opts.fallbackTitle, width / 2, 13, 0xFFFFFF);
super.drawScreen(mx, my, pt);
}

View File

@ -17,7 +17,6 @@
package net.lax1dude.eaglercraft.v1_8.webview;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.PauseMenuCustomizeState;
import net.lax1dude.eaglercraft.v1_8.internal.WebViewOptions;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
@ -77,7 +76,8 @@ public class GuiScreenServerInfoDesktop extends GuiScreen {
public void drawScreen(int mx, int my, float pt) {
drawDefaultBackground();
drawCenteredString(fontRendererObj, PauseMenuCustomizeState.serverInfoEmbedTitle, this.width / 2, 70, 16777215);
drawCenteredString(fontRendererObj, opts.fallbackTitle == null ? "Server Info" : opts.fallbackTitle,
this.width / 2, 70, 16777215);
drawCenteredString(fontRendererObj, I18n.format("fallbackWebViewScreen.text0"), this.width / 2, 90, 11184810);
String link = WebViewOverlayController.fallbackRunning() ? WebViewOverlayController.getFallbackURL()
: I18n.format(hasStarted ? "fallbackWebViewScreen.exited" : "fallbackWebViewScreen.startingUp");