mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-27 18:38:14 -05:00
Update #51 - Protocol and FPS improvements, better workspace
This commit is contained in:
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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()");
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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'
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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'
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
Reference in New Issue
Block a user