open sauce

This commit is contained in:
ayunami2000 2023-09-25 12:46:10 -04:00
commit 0dba9a05e6
19 changed files with 1599 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.gradle
.idea
build
gradle
libs
gradlew
gradlew.bat
src/main/resources/*.png

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2023, ayunami2000
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
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.

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# ayunViaProxyEagUtils
eaglercraft support for viaproxy. ws:// on same port as java. supports both 1.5.2 and 1.8.8 eaglercraft depending on the configuration (e.g. use legacy passthrough & protocolsupport if the backend server doesn't support 1.5.2)
note: skin files from 1.5.2 are excluded

14
build.gradle Normal file
View File

@ -0,0 +1,14 @@
plugins {
id 'java'
}
group 'me.ayunami2000.ayunViaProxyEagUtils'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation files("libs/ViaProxy-3.0.21-SNAPSHOT+java8_PATCHED.jar")
}

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'ayunViaProxyEagUtils'

View File

@ -0,0 +1,163 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class EaglerSkinHandler extends ChannelInboundHandlerAdapter {
public static final ConcurrentHashMap<UUID, byte[]> skinCollection;
private static final ConcurrentHashMap<UUID, byte[]> capeCollection;
private static final ConcurrentHashMap<UUID, Long> lastSkinLayerUpdate;
private static final int[] SKIN_DATA_SIZE;
private static final int[] CAPE_DATA_SIZE;
private static final ConcurrentHashMap<UUID, ChannelHandlerContext> users;
private final String user;
private static void sendData(final ChannelHandlerContext ctx, final String channel, final byte[] data) throws IOException {
final ByteBuf bb = ctx.alloc().buffer();
bb.writeByte(250);
try {
Types1_6_4.STRING.write(bb, channel);
} catch (Exception e) {
throw new IOException(e);
}
bb.writeShort(data.length);
bb.writeBytes(data);
ctx.writeAndFlush(new BinaryWebSocketFrame(bb));
}
public EaglerSkinHandler(final String username) {
this.user = username;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
public void channelRead(final ChannelHandlerContext ctx, final Object obj) throws Exception {
final UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8));
if (!EaglerSkinHandler.users.containsKey(uuid) && ctx.channel().isActive()) {
EaglerSkinHandler.users.put(uuid, ctx);
}
if (obj instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) obj).content();
if (bb.readableBytes() >= 3 && bb.readByte() == -6) {
String tag;
byte[] msg;
try {
tag = Types1_6_4.STRING.read(bb);
msg = new byte[bb.readShort()];
bb.readBytes(msg);
} catch (Exception e) {
bb.resetReaderIndex();
super.channelRead(ctx, obj);
return;
}
try {
if ("EAG|MySkin".equals(tag)) {
if (!EaglerSkinHandler.skinCollection.containsKey(uuid)) {
final int t = msg[0] & 0xFF;
if (t < EaglerSkinHandler.SKIN_DATA_SIZE.length && msg.length == EaglerSkinHandler.SKIN_DATA_SIZE[t] + 1) {
EaglerSkinHandler.skinCollection.put(uuid, msg);
}
}
bb.release();
return;
}
if ("EAG|MyCape".equals(tag)) {
if (!EaglerSkinHandler.capeCollection.containsKey(uuid)) {
final int t = msg[0] & 0xFF;
if (t < EaglerSkinHandler.CAPE_DATA_SIZE.length && msg.length == EaglerSkinHandler.CAPE_DATA_SIZE[t] + 2) {
EaglerSkinHandler.capeCollection.put(uuid, msg);
}
}
bb.release();
return;
}
if ("EAG|FetchSkin".equals(tag)) {
if (msg.length > 2) {
final String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8);
final UUID uuidFetch = UUID.nameUUIDFromBytes(("OfflinePlayer:" + fetch).getBytes(StandardCharsets.UTF_8));
byte[] data;
if ((data = EaglerSkinHandler.skinCollection.get(uuidFetch)) != null) {
byte[] conc = new byte[data.length + 2];
conc[0] = msg[0];
conc[1] = msg[1];
System.arraycopy(data, 0, conc, 2, data.length);
if ((data = EaglerSkinHandler.capeCollection.get(uuidFetch)) != null) {
final byte[] conc2 = new byte[conc.length + data.length];
System.arraycopy(conc, 0, conc2, 0, conc.length);
System.arraycopy(data, 0, conc2, conc.length, data.length);
conc = conc2;
}
sendData(ctx, "EAG|UserSkin", conc);
}
}
bb.release();
return;
}
if ("EAG|SkinLayers".equals(tag)) {
final long millis = System.currentTimeMillis();
final Long lsu = EaglerSkinHandler.lastSkinLayerUpdate.get(uuid);
if (lsu != null && millis - lsu < 700L) {
return;
}
EaglerSkinHandler.lastSkinLayerUpdate.put(uuid, millis);
byte[] conc2;
if ((conc2 = EaglerSkinHandler.capeCollection.get(uuid)) != null) {
conc2[1] = msg[0];
} else {
conc2 = new byte[]{2, msg[0], 0};
EaglerSkinHandler.capeCollection.put(uuid, conc2);
}
final ByteArrayOutputStream bao = new ByteArrayOutputStream();
final DataOutputStream dd = new DataOutputStream(bao);
dd.write(msg[0]);
dd.writeUTF(this.user);
final byte[] bpacket = bao.toByteArray();
for (final UUID pl : EaglerSkinHandler.users.keySet()) {
if (!pl.equals(uuid)) {
sendData(EaglerSkinHandler.users.get(pl), "EAG|SkinLayers", bpacket);
}
}
bb.release();
return;
}
} catch (Throwable var18) {
var18.printStackTrace();
}
}
bb.resetReaderIndex();
}
super.channelRead(ctx, obj);
}
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
final UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8));
EaglerSkinHandler.users.remove(uuid);
EaglerSkinHandler.skinCollection.remove(uuid);
EaglerSkinHandler.capeCollection.remove(uuid);
EaglerSkinHandler.lastSkinLayerUpdate.remove(uuid);
}
static {
skinCollection = new ConcurrentHashMap<>();
capeCollection = new ConcurrentHashMap<>();
lastSkinLayerUpdate = new ConcurrentHashMap<>();
SKIN_DATA_SIZE = new int[]{8192, 16384, -9, -9, 1, 16384, -9};
CAPE_DATA_SIZE = new int[]{4096, -9, 1};
users = new ConcurrentHashMap<>();
}
}

View File

@ -0,0 +1,321 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.util.AttributeKey;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
public class EaglerVoiceHandler extends ChannelInboundHandlerAdapter {
private static final ConcurrentHashMap<String, ChannelHandlerContext> voicePlayers;
private static final ConcurrentHashMap<String, ExpiringSet<String>> voiceRequests;
private static final CopyOnWriteArraySet<String[]> voicePairs;
private final String user;
private static final Collection<String> iceServers;
private static final AttributeKey<Boolean> VOICE_ENABLED;
private static void sendData(final ChannelHandlerContext ctx, final byte[] data) throws IOException {
final ByteBuf bb = ctx.alloc().buffer();
bb.writeByte(250);
try {
Types1_6_4.STRING.write(bb, "EAG|Voice");
} catch (Exception e) {
throw new IOException(e);
}
bb.writeShort(data.length);
bb.writeBytes(data);
ctx.writeAndFlush(new BinaryWebSocketFrame(bb));
}
public EaglerVoiceHandler(final String username) {
this.user = username;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
public void channelRead(final ChannelHandlerContext ctx, final Object obj) throws Exception {
if (((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).state == EaglercraftHandler.State.LOGIN_COMPLETE && !ctx.channel().hasAttr((AttributeKey) EaglerVoiceHandler.VOICE_ENABLED)) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
dos.write(0);
dos.writeBoolean(true);
dos.write(EaglerVoiceHandler.iceServers.size());
for (final String str : EaglerVoiceHandler.iceServers) {
dos.writeUTF(str);
}
sendData(ctx, baos.toByteArray());
this.sendVoicePlayers(this.user);
ctx.channel().attr((AttributeKey) EaglerVoiceHandler.VOICE_ENABLED).set(true);
}
if (obj instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) obj).content();
if (bb.readableBytes() >= 3 && bb.readByte() == -6) {
String tag;
byte[] msg;
try {
tag = Types1_6_4.STRING.read(bb);
msg = new byte[bb.readShort()];
bb.readBytes(msg);
} catch (Exception e) {
bb.resetReaderIndex();
super.channelRead(ctx, obj);
return;
}
try {
if (!tag.equals("EAG|Voice")) {
bb.resetReaderIndex();
super.channelRead(ctx, obj);
return;
}
final DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg));
final int sig = streamIn.read();
switch (sig) {
case 0: {
if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) {
bb.release();
return;
}
final String targetUser = streamIn.readUTF();
if (this.user.equals(targetUser)) {
bb.release();
return;
}
if (this.checkVoicePair(this.user, targetUser)) {
bb.release();
return;
}
if (!EaglerVoiceHandler.voicePlayers.containsKey(targetUser)) {
bb.release();
return;
}
if (!EaglerVoiceHandler.voiceRequests.containsKey(this.user)) {
EaglerVoiceHandler.voiceRequests.put(this.user, new ExpiringSet<>(2000L));
}
if (!EaglerVoiceHandler.voiceRequests.get(this.user).contains(targetUser)) {
EaglerVoiceHandler.voiceRequests.get(this.user).add(targetUser);
if (EaglerVoiceHandler.voiceRequests.containsKey(targetUser) && EaglerVoiceHandler.voiceRequests.get(targetUser).contains(this.user)) {
if (EaglerVoiceHandler.voiceRequests.containsKey(targetUser)) {
EaglerVoiceHandler.voiceRequests.get(targetUser).remove(this.user);
if (EaglerVoiceHandler.voiceRequests.get(targetUser).isEmpty()) {
EaglerVoiceHandler.voiceRequests.remove(targetUser);
}
}
if (EaglerVoiceHandler.voiceRequests.containsKey(this.user)) {
EaglerVoiceHandler.voiceRequests.get(this.user).remove(targetUser);
if (EaglerVoiceHandler.voiceRequests.get(this.user).isEmpty()) {
EaglerVoiceHandler.voiceRequests.remove(this.user);
}
}
EaglerVoiceHandler.voicePairs.add(new String[]{this.user, targetUser});
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.write(1);
dos2.writeUTF(this.user);
dos2.writeBoolean(false);
sendData(EaglerVoiceHandler.voicePlayers.get(targetUser), baos2.toByteArray());
baos2 = new ByteArrayOutputStream();
dos2 = new DataOutputStream(baos2);
dos2.write(1);
dos2.writeUTF(targetUser);
dos2.writeBoolean(true);
sendData(ctx, baos2.toByteArray());
}
bb.release();
return;
}
bb.release();
return;
}
case 1: {
if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) {
final ByteArrayOutputStream baos3 = new ByteArrayOutputStream();
final DataOutputStream dos3 = new DataOutputStream(baos3);
dos3.write(1);
dos3.writeUTF(this.user);
final byte[] out = baos3.toByteArray();
for (final ChannelHandlerContext conn : EaglerVoiceHandler.voicePlayers.values()) {
sendData(conn, out);
}
EaglerVoiceHandler.voicePlayers.put(this.user, ctx);
for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) {
this.sendVoicePlayers(username);
}
bb.release();
return;
}
bb.release();
return;
}
case 2: {
if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) {
bb.release();
return;
}
try {
final String targetUser = streamIn.readUTF();
if (!EaglerVoiceHandler.voicePlayers.containsKey(targetUser)) {
bb.release();
return;
}
if (EaglerVoiceHandler.voicePairs.removeIf(pair -> (pair[0].equals(this.user) && pair[1].equals(targetUser)) || (pair[0].equals(targetUser) && pair[1].equals(this.user)))) {
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.write(2);
dos2.writeUTF(this.user);
sendData(EaglerVoiceHandler.voicePlayers.get(targetUser), baos2.toByteArray());
baos2 = new ByteArrayOutputStream();
dos2 = new DataOutputStream(baos2);
dos2.write(2);
dos2.writeUTF(targetUser);
sendData(ctx, baos2.toByteArray());
break;
}
} catch (EOFException var7) {
this.removeUser(this.user);
}
bb.release();
return;
}
case 3:
case 4: {
if (EaglerVoiceHandler.voicePlayers.containsKey(this.user)) {
final String username = streamIn.readUTF();
if (this.checkVoicePair(this.user, username)) {
final String data = streamIn.readUTF();
final ByteArrayOutputStream baos4 = new ByteArrayOutputStream();
final DataOutputStream dos4 = new DataOutputStream(baos4);
dos4.write(sig);
dos4.writeUTF(this.user);
dos4.writeUTF(data);
sendData(EaglerVoiceHandler.voicePlayers.get(username), baos4.toByteArray());
}
bb.release();
return;
}
bb.release();
return;
}
default: {
bb.release();
return;
}
}
} catch (Throwable var8) {
this.removeUser(this.user);
bb.release();
return;
}
bb.release();
return;
}
bb.resetReaderIndex();
}
super.channelRead(ctx, obj);
}
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
this.removeUser(this.user);
}
public void sendVoicePlayers(final String name) {
if (EaglerVoiceHandler.voicePlayers.containsKey(name)) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
dos.write(5);
final Set<String> mostlyGlobalPlayers = new HashSet<>();
for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) {
if (!username.equals(name) && EaglerVoiceHandler.voicePairs.stream().noneMatch(pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name)))) {
mostlyGlobalPlayers.add(username);
}
}
if (mostlyGlobalPlayers.size() > 0) {
dos.writeInt(mostlyGlobalPlayers.size());
for (final String username : mostlyGlobalPlayers) {
dos.writeUTF(username);
}
sendData(EaglerVoiceHandler.voicePlayers.get(name), baos.toByteArray());
}
} catch (IOException ignored) {
}
}
}
public void removeUser(final String name) {
EaglerVoiceHandler.voicePlayers.remove(name);
for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) {
if (!name.equals(username)) {
this.sendVoicePlayers(username);
}
}
for (final String[] voicePair : EaglerVoiceHandler.voicePairs) {
String target = null;
if (voicePair[0].equals(name)) {
target = voicePair[1];
} else if (voicePair[1].equals(name)) {
target = voicePair[0];
}
if (target != null && EaglerVoiceHandler.voicePlayers.containsKey(target)) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
dos.write(2);
dos.writeUTF(name);
sendData(EaglerVoiceHandler.voicePlayers.get(target), baos.toByteArray());
} catch (IOException ignored) {
}
}
}
EaglerVoiceHandler.voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name));
}
private boolean checkVoicePair(final String user1, final String user2) {
return EaglerVoiceHandler.voicePairs.stream().anyMatch(pair -> (pair[0].equals(user1) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user1)));
}
static {
voicePlayers = new ConcurrentHashMap<>();
voiceRequests = new ConcurrentHashMap<>();
voicePairs = new CopyOnWriteArraySet<>();
(iceServers = new ArrayList<>()).add("stun:stun.l.google.com:19302");
EaglerVoiceHandler.iceServers.add("stun:stun1.l.google.com:19302");
EaglerVoiceHandler.iceServers.add("stun:stun2.l.google.com:19302");
EaglerVoiceHandler.iceServers.add("stun:stun3.l.google.com:19302");
EaglerVoiceHandler.iceServers.add("stun:stun4.l.google.com:19302");
EaglerVoiceHandler.iceServers.add("stun:openrelay.metered.ca:80");
final Map<String, Object> turnServerList = new HashMap<>();
HashMap<String, Object> n = new HashMap<>();
n.put("url", "turn:openrelay.metered.ca:80");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay1", n);
n = new HashMap<>();
n.put("url", "turn:openrelay.metered.ca:443");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay2", n);
n = new HashMap<>();
n.put("url", "turn:openrelay.metered.ca:443?transport=tcp");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay3", n);
for (final Map.Entry<String, Object> trn : turnServerList.entrySet()) {
final Object o = trn.getValue();
if (o instanceof Map) {
final Map o2 = (Map) o;
EaglerVoiceHandler.iceServers.add("" + o2.get("url") + ";" + o2.get("username") + ";" + o2.get("password"));
}
}
VOICE_ENABLED = AttributeKey.valueOf("ayun-voice-enabled");
}
}

View File

@ -0,0 +1,35 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
public class EaglerXLoginHandler extends ChannelOutboundHandlerAdapter {
private int counter;
public EaglerXLoginHandler() {
this.counter = 0;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception {
if (msg instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) msg).content();
if (PacketTypes.readVarInt(bb) == 2 && ++this.counter == 2) {
ctx.pipeline().remove("ayun-eag-x-login");
bb.writerIndex(bb.readerIndex());
PacketTypes.writeString(bb, "");
bb.writeByte(2);
}
bb.resetReaderIndex();
}
super.write(ctx, msg, promise);
}
}

View File

@ -0,0 +1,107 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class EaglerXSkinHandler extends ChannelInboundHandlerAdapter {
private final ConcurrentHashMap<String, byte[]> profileData;
public static final SkinService skinService;
private String user;
private int pluginMessageId;
public EaglerXSkinHandler() {
this.profileData = new ConcurrentHashMap<>();
this.pluginMessageId = -1;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
public void channelRead(final ChannelHandlerContext ctx, final Object obj) throws Exception {
final EaglercraftHandler.State state = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).state;
if (state == EaglercraftHandler.State.LOGIN && obj instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) obj).content();
if (bb.readUnsignedByte() == 7) {
if (this.profileData.size() > 12) {
ctx.close();
bb.release();
return;
}
int strlen = bb.readUnsignedByte();
final String dataType = bb.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = bb.readUnsignedShort();
final byte[] readData = new byte[strlen];
bb.readBytes(readData);
if (bb.isReadable()) {
ctx.close();
bb.release();
return;
}
if (this.profileData.containsKey(dataType)) {
ctx.close();
bb.release();
return;
}
this.profileData.put(dataType, readData);
}
bb.resetReaderIndex();
}
if (state != EaglercraftHandler.State.LOGIN_COMPLETE) {
super.channelRead(ctx, obj);
return;
}
if (this.user == null) {
this.user = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).username;
final UUID clientUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8));
if (this.profileData.containsKey("skin_v1")) {
try {
SkinPackets.registerEaglerPlayer(clientUUID, this.profileData.get("skin_v1"), EaglerXSkinHandler.skinService);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXSkinHandler.skinService);
}
} else {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXSkinHandler.skinService);
}
}
if (this.pluginMessageId <= 0) {
this.pluginMessageId = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).pluginMessageId;
}
if (obj instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) obj).content();
try {
if (PacketTypes.readVarInt(bb) == this.pluginMessageId && PacketTypes.readString(bb, 32767).equals("EAG|Skins-1.8")) {
final byte[] data = new byte[bb.readableBytes()];
bb.readBytes(data);
SkinPackets.processPacket(data, ctx, EaglerXSkinHandler.skinService);
bb.release();
return;
}
} catch (Exception ignored) {
}
bb.resetReaderIndex();
}
super.channelRead(ctx, obj);
}
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
if (this.user != null) {
EaglerXSkinHandler.skinService.unregisterPlayer(UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8)));
}
}
static {
skinService = new SkinService();
}
}

View File

@ -0,0 +1,355 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import com.google.common.net.HostAndPort;
import com.viaversion.viaversion.libs.gson.JsonArray;
import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.libs.gson.JsonParser;
import com.viaversion.viaversion.protocols.base.ClientboundStatusPackets;
import com.viaversion.viaversion.protocols.base.ServerboundHandshakePackets;
import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets;
import com.viaversion.viaversion.protocols.base.ServerboundStatusPackets;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import net.lenni0451.mcstructs.text.serializer.TextComponentSerializer;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ServerboundPackets1_5_2;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
import net.raphimc.vialoader.util.VersionEnum;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import net.raphimc.viaproxy.util.logging.Logger;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
public class EaglercraftHandler extends MessageToMessageCodec<WebSocketFrame, ByteBuf> {
private HostAndPort host;
public State state;
public VersionEnum version;
public int pluginMessageId;
public String username;
public EaglercraftHandler() {
this.state = State.PRE_HANDSHAKE;
}
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(-2);
ctx.pipeline().remove("sizer");
super.channelActive(ctx);
}
protected void encode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws IOException {
if (this.state == State.STATUS) {
final int packetId = PacketTypes.readVarInt(in);
if (packetId != ClientboundStatusPackets.STATUS_RESPONSE.getId()) {
throw new IllegalStateException("Unexpected packet id " + packetId);
}
final JsonObject root = JsonParser.parseString(PacketTypes.readString(in, 32767)).getAsJsonObject();
final JsonObject response = new JsonObject();
response.addProperty("name", "ViaProxy");
response.addProperty("brand", "ViaProxy");
if (root.has("version")) {
response.add("vers", root.getAsJsonObject("version").get("name"));
} else {
response.addProperty("vers", "Unknown");
}
response.addProperty("cracked", Boolean.TRUE);
response.addProperty("secure", Boolean.FALSE);
response.addProperty("time", System.currentTimeMillis());
response.addProperty("uuid", UUID.randomUUID().toString());
response.addProperty("type", "motd");
final JsonObject data = new JsonObject();
data.addProperty("cache", Boolean.FALSE);
final JsonArray motd = new JsonArray();
if (root.has("description")) {
final String[] split = TextComponentSerializer.V1_8.deserialize(root.get("description").toString()).asLegacyFormatString().split("\n");
for (final String motdLine : split) {
motd.add(motdLine);
}
}
data.add("motd", motd);
data.addProperty("icon", root.has("favicon"));
if (root.has("players")) {
final JsonObject javaPlayers = root.getAsJsonObject("players");
data.add("online", javaPlayers.get("online"));
data.add("max", javaPlayers.get("max"));
final JsonArray players = new JsonArray();
if (javaPlayers.has("sample")) {
javaPlayers.getAsJsonArray("sample").forEach(player -> players.add(TextComponentSerializer.V1_8.deserialize(player.getAsJsonObject().get("name").getAsString()).asLegacyFormatString()));
}
data.add("players", players);
}
response.add("data", data);
out.add(new TextWebSocketFrame(response.toString()));
if (root.has("favicon")) {
final BufferedImage icon = ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(root.get("favicon").getAsString().substring(22).replace("\n", "").getBytes(StandardCharsets.UTF_8))));
final int[] pixels = icon.getRGB(0, 0, 64, 64, null, 0, 64);
final byte[] iconPixels = new byte[16384];
for (int i = 0; i < 4096; ++i) {
iconPixels[i * 4] = (byte) (pixels[i] >> 16 & 0xFF);
iconPixels[i * 4 + 1] = (byte) (pixels[i] >> 8 & 0xFF);
iconPixels[i * 4 + 2] = (byte) (pixels[i] & 0xFF);
iconPixels[i * 4 + 3] = (byte) (pixels[i] >> 24 & 0xFF);
}
out.add(new BinaryWebSocketFrame(ctx.alloc().buffer().writeBytes(iconPixels)));
}
} else {
if (this.state != State.LOGIN_COMPLETE) {
throw new IllegalStateException("Cannot send packets before login is completed");
}
out.add(new BinaryWebSocketFrame(in.retain()));
}
}
protected void decode(final ChannelHandlerContext ctx, final WebSocketFrame in, final List<Object> out) throws Exception {
if (in instanceof BinaryWebSocketFrame) {
final ByteBuf data = in.content();
switch (this.state) {
case PRE_HANDSHAKE: {
if (data.readableBytes() >= 2 && data.getByte(0) == 2 && data.getByte(1) == 69) {
data.setByte(1, 61);
this.state = State.LOGIN_COMPLETE;
this.version = VersionEnum.r1_5_2;
out.add(data.retain());
break;
}
this.state = State.HANDSHAKE;
}
case HANDSHAKE: {
final int packetId = data.readUnsignedByte();
if (packetId != 1) {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
int eaglercraftVersion = data.readUnsignedByte();
int minecraftVersion;
if (eaglercraftVersion == 1) {
minecraftVersion = data.readUnsignedByte();
} else {
if (eaglercraftVersion != 2) {
throw new IllegalArgumentException("Unknown Eaglercraft version: " + eaglercraftVersion);
}
int count = data.readUnsignedShort();
final List<Integer> eaglercraftVersions = new ArrayList<>(count);
for (int i = 0; i < count; ++i) {
eaglercraftVersions.add(data.readUnsignedShort());
}
if (!eaglercraftVersions.contains(2) && !eaglercraftVersions.contains(3)) {
Logger.LOGGER.error("No supported eaglercraft versions found");
ctx.close();
return;
}
if (eaglercraftVersions.contains(3)) {
eaglercraftVersion = 3;
}
count = data.readUnsignedShort();
final List<Integer> minecraftVersions = new ArrayList<>(count);
for (int j = 0; j < count; ++j) {
minecraftVersions.add(data.readUnsignedShort());
}
if (minecraftVersions.size() != 1) {
Logger.LOGGER.error("No supported minecraft versions found");
ctx.close();
}
minecraftVersion = minecraftVersions.get(0);
}
final String clientBrand = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
final String clientVersionString = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
if (eaglercraftVersion >= 2) {
data.skipBytes(1);
data.skipBytes(data.readUnsignedByte());
}
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
Logger.LOGGER.info("Eaglercraft client connected: " + clientBrand + " " + clientVersionString);
this.state = State.HANDSHAKE_COMPLETE;
this.version = VersionEnum.fromProtocolId(minecraftVersion);
if (this.version.equals(VersionEnum.UNKNOWN)) {
Logger.LOGGER.error("Unsupported protocol version: " + minecraftVersion);
ctx.close();
return;
}
final ByteBuf response = ctx.alloc().buffer();
response.writeByte(2);
if (eaglercraftVersion == 1) {
response.writeByte(1);
} else {
response.writeShort(eaglercraftVersion);
response.writeShort(minecraftVersion);
}
response.writeByte("ViaProxy".length()).writeCharSequence("ViaProxy", StandardCharsets.US_ASCII);
response.writeByte(ViaProxy.VERSION.length()).writeCharSequence(ViaProxy.VERSION, StandardCharsets.US_ASCII);
response.writeByte(0);
response.writeShort(0);
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
break;
}
case HANDSHAKE_COMPLETE: {
final int packetId = data.readUnsignedByte();
if (packetId != 4) {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
final String username = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
data.skipBytes(data.readUnsignedByte());
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
this.state = State.LOGIN;
this.username = username;
final UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
final ByteBuf response2 = ctx.alloc().buffer();
response2.writeByte(5);
response2.writeByte(username.length()).writeCharSequence(username, StandardCharsets.US_ASCII);
response2.writeLong(uuid.getMostSignificantBits()).writeLong(uuid.getLeastSignificantBits());
ctx.writeAndFlush(new BinaryWebSocketFrame(response2));
break;
}
case LOGIN: {
final int packetId = data.readUnsignedByte();
if (packetId == 7) {
data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
final byte[] dataBytes = new byte[data.readUnsignedShort()];
data.readBytes(dataBytes);
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
} else {
if (packetId != 8) {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
this.state = State.LOGIN_COMPLETE;
this.pluginMessageId = MCPackets.C2S_PLUGIN_MESSAGE.getId(this.version.getVersion());
if (this.pluginMessageId == -1) {
Logger.LOGGER.error("Unsupported protocol version: " + this.version.getVersion());
ctx.close();
return;
}
if (ctx.pipeline().get("legacy-passthrough-handler") != null) {
ctx.pipeline().remove("legacy-passthrough-handler");
}
out.add(this.writeHandshake(ctx.alloc().buffer(), ConnectionState.LOGIN));
final ByteBuf loginHello = ctx.alloc().buffer();
PacketTypes.writeVarInt(loginHello, ServerboundLoginPackets.HELLO.getId());
PacketTypes.writeString(loginHello, this.username);
out.add(loginHello);
final ByteBuf response3 = ctx.alloc().buffer();
response3.writeByte(9);
ctx.writeAndFlush(new BinaryWebSocketFrame(response3));
}
break;
}
case LOGIN_COMPLETE: {
if (this.version.equals(VersionEnum.r1_5_2)) {
final int packetId = data.readUnsignedByte();
if (packetId == ServerboundPackets1_5_2.SHARED_KEY.getId()) {
ctx.channel().writeAndFlush(new BinaryWebSocketFrame(data.readerIndex(0).retain()));
break;
}
if (packetId == ServerboundPackets1_5_2.PLUGIN_MESSAGE.getId() && Types1_6_4.STRING.read(data).startsWith("EAG|")) {
break;
}
} else if (this.version.isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) {
final int packetId = PacketTypes.readVarInt(data);
if (packetId == this.pluginMessageId && PacketTypes.readString(data, 32767).startsWith("EAG|")) {
break;
}
}
out.add(data.readerIndex(0).retain());
break;
}
default: {
throw new IllegalStateException("Unexpected binary frame in state " + this.state);
}
}
} else {
if (!(in instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException("Unsupported frame type: " + in.getClass().getName());
}
final String text = ((TextWebSocketFrame) in).text();
if (this.state != State.PRE_HANDSHAKE) {
throw new IllegalStateException("Unexpected text frame in state " + this.state);
}
if (!text.equalsIgnoreCase("accept: motd")) {
ctx.close();
return;
}
this.state = State.STATUS;
this.version = VersionEnum.r1_8;
if (ctx.pipeline().get("legacy-passthrough-handler") != null) {
ctx.pipeline().remove("legacy-passthrough-handler");
}
out.add(this.writeHandshake(ctx.alloc().buffer(), ConnectionState.STATUS));
final ByteBuf statusRequest = ctx.alloc().buffer();
PacketTypes.writeVarInt(statusRequest, ServerboundStatusPackets.STATUS_REQUEST.getId());
out.add(statusRequest);
}
}
public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
final WebSocketServerProtocolHandler.HandshakeComplete handshake = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
if (!handshake.requestHeaders().contains("Host")) {
ctx.close();
return;
}
this.host = HostAndPort.fromString(handshake.requestHeaders().get("Host")).withDefaultPort(80);
}
super.userEventTriggered(ctx, evt);
}
private ByteBuf writeHandshake(final ByteBuf byteBuf, final ConnectionState state) {
PacketTypes.writeVarInt(byteBuf, ServerboundHandshakePackets.CLIENT_INTENTION.getId());
PacketTypes.writeVarInt(byteBuf, this.version.getVersion());
PacketTypes.writeString(byteBuf, this.host.getHost());
byteBuf.writeShort(this.host.getPort());
int i;
switch (state) {
case PLAY:
i = 0;
break;
case STATUS:
i = 1;
break;
case LOGIN:
i = 2;
break;
default:
i = -1;
}
PacketTypes.writeVarInt(byteBuf, i);
return byteBuf;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
public enum State {
STATUS,
PRE_HANDSHAKE,
HANDSHAKE,
HANDSHAKE_COMPLETE,
LOGIN,
LOGIN_COMPLETE
}
}

View File

@ -0,0 +1,70 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class EaglercraftInitialHandler extends ByteToMessageDecoder {
private static SslContext sslContext;
protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) {
if (!ctx.channel().isOpen()) {
return;
}
if (!in.isReadable()) {
return;
}
if (in.readableBytes() >= 3 || in.getByte(0) != 71) {
if (in.readableBytes() >= 3 && in.getCharSequence(0, 3, StandardCharsets.UTF_8).equals("GET")) {
if (EaglercraftInitialHandler.sslContext != null) {
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-ssl-handler", EaglercraftInitialHandler.sslContext.newHandler(ctx.alloc()));
}
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-http-codec", new HttpServerCodec());
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-http-aggregator", new HttpObjectAggregator(65535, true));
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-compression", new WebSocketServerCompressionHandler());
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-handler", new WebSocketServerProtocolHandler("/", null, true));
ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-active-notifier", new WebSocketActiveNotifier());
ctx.pipeline().addBefore("eaglercraft-initial-handler", "eaglercraft-handler", new EaglercraftHandler());
ctx.fireUserEventTriggered(EaglercraftClientConnected.INSTANCE);
ctx.pipeline().fireChannelRead(in.readBytes(in.readableBytes()));
} else {
out.add(in.readBytes(in.readableBytes()));
}
ctx.pipeline().remove(this);
}
}
static {
final File certFolder = new File("certs");
if (certFolder.exists()) {
try {
EaglercraftInitialHandler.sslContext = SslContextBuilder.forServer(new File(certFolder, "fullchain.pem"), new File(certFolder, "privkey.pem")).build();
} catch (Throwable e) {
throw new RuntimeException("Failed to load SSL context", e);
}
}
}
public static final class EaglercraftClientConnected {
public static final EaglercraftClientConnected INSTANCE;
static {
INSTANCE = new EaglercraftClientConnected();
}
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
}

View File

@ -0,0 +1,78 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
public class ExpiringSet<T> extends HashSet<T> {
private final long expiration;
private final ExpiringEvent<T> event;
private final Map<T, Long> timestamps;
public ExpiringSet(final long expiration) {
this.timestamps = new HashMap<>();
this.expiration = expiration;
this.event = null;
}
public ExpiringSet(final long expiration, final ExpiringEvent<T> event) {
this.timestamps = new HashMap<>();
this.expiration = expiration;
this.event = event;
}
public void checkForExpirations() {
final Iterator<T> iterator = this.timestamps.keySet().iterator();
final long now = System.currentTimeMillis();
while (iterator.hasNext()) {
final T element = iterator.next();
if (super.contains(element)) {
if (this.timestamps.get(element) + this.expiration >= now) {
continue;
}
if (this.event != null) {
this.event.onExpiration(element);
}
}
iterator.remove();
super.remove(element);
}
}
@Override
public boolean add(final T o) {
this.checkForExpirations();
final boolean success = super.add(o);
if (success) {
this.timestamps.put(o, System.currentTimeMillis());
}
return success;
}
@Override
public boolean remove(final Object o) {
this.checkForExpirations();
final boolean success = super.remove(o);
if (success) {
this.timestamps.remove(o);
}
return success;
}
@Override
public void clear() {
this.timestamps.clear();
super.clear();
}
@Override
public boolean contains(final Object o) {
this.checkForExpirations();
return super.contains(o);
}
public interface ExpiringEvent<T> {
void onExpiration(final T p0);
}
}

View File

@ -0,0 +1,67 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.lenni0451.lambdaevents.EventHandler;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
public class Main extends ViaProxyPlugin {
public void onEnable() {
PluginManager.EVENT_MANAGER.register(this);
}
@EventHandler
public void onEvent(final Client2ProxyChannelInitializeEvent event) {
if (event.getType() == ITyped.Type.PRE) {
event.getChannel().pipeline().addLast("eaglercraft-initial-handler", new EaglercraftInitialHandler());
}
if (event.getType() == ITyped.Type.POST) {
event.getChannel().pipeline().addAfter("eaglercraft-initial-handler", "ayun-eag-detector", new EaglerConnectionHandler());
}
}
static class EaglerConnectionHandler extends ChannelInboundHandlerAdapter {
public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
if (evt instanceof EaglercraftInitialHandler.EaglercraftClientConnected) {
ctx.pipeline().remove("ayun-eag-detector");
ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-utils-init", new EaglerUtilsInitHandler());
}
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
}
static class EaglerUtilsInitHandler extends ChannelInboundHandlerAdapter {
public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof BinaryWebSocketFrame) {
final ByteBuf bb = ((BinaryWebSocketFrame) msg).content();
try {
if (bb.readByte() == 2 && bb.readByte() == 69) {
final String username = Types1_6_4.STRING.read(bb);
ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-voice", new EaglerVoiceHandler(username));
ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-skin", new EaglerSkinHandler(username));
} else {
ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-x-login", new EaglerXLoginHandler());
ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-skin-x", new EaglerXSkinHandler());
}
} catch (Exception ignored) {
}
bb.resetReaderIndex();
}
ctx.pipeline().remove("ayun-eag-utils-init");
super.channelRead(ctx, msg);
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
}
}

View File

@ -0,0 +1,42 @@
package me.ayunami2000.ayunViaProxyEagUtils;
public class SkinConverter {
public static void convert64x32to64x64(final int[] skinIn, final int[] skinOut) {
copyRawPixels(skinIn, skinOut, 0, 0, 0, 0, 64, 32, false);
copyRawPixels(skinIn, skinOut, 24, 48, 20, 4, 16, 8, 20);
copyRawPixels(skinIn, skinOut, 28, 48, 24, 8, 16, 12, 20);
copyRawPixels(skinIn, skinOut, 20, 52, 16, 8, 20, 12, 32);
copyRawPixels(skinIn, skinOut, 24, 52, 20, 4, 20, 8, 32);
copyRawPixels(skinIn, skinOut, 28, 52, 24, 0, 20, 4, 32);
copyRawPixels(skinIn, skinOut, 32, 52, 28, 12, 20, 16, 32);
copyRawPixels(skinIn, skinOut, 40, 48, 36, 44, 16, 48, 20);
copyRawPixels(skinIn, skinOut, 44, 48, 40, 48, 16, 52, 20);
copyRawPixels(skinIn, skinOut, 36, 52, 32, 48, 20, 52, 32);
copyRawPixels(skinIn, skinOut, 40, 52, 36, 44, 20, 48, 32);
copyRawPixels(skinIn, skinOut, 44, 52, 40, 40, 20, 44, 32);
copyRawPixels(skinIn, skinOut, 48, 52, 44, 52, 20, 56, 32);
}
private static void copyRawPixels(final int[] imageIn, final int[] imageOut, final int dx1, final int dy1, final int dx2, final int sx1, final int sy1, final int sx2, final int sy2) {
if (dx1 > dx2) {
copyRawPixels(imageIn, imageOut, sx1, sy1, dx2, dy1, sx2 - sx1, sy2 - sy1, true);
} else {
copyRawPixels(imageIn, imageOut, sx1, sy1, dx1, dy1, sx2 - sx1, sy2 - sy1, false);
}
}
private static void copyRawPixels(final int[] imageIn, final int[] imageOut, final int srcX, final int srcY, final int dstX, final int dstY, final int width, final int height, final boolean flip) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
final int i = imageIn[(srcY + y) * 64 + srcX + x];
int j;
if (flip) {
j = (dstY + y) * 64 + dstX + width - x - 1;
} else {
j = (dstY + y) * 64 + dstX + x;
}
imageOut[j] = i;
}
}
}
}

View File

@ -0,0 +1,146 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.channel.ChannelHandlerContext;
import java.io.IOException;
import java.util.UUID;
public class SkinPackets {
public static void processPacket(final byte[] data, final ChannelHandlerContext sender, final SkinService skinService) throws IOException {
if (data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
final int packetId = data[0] & 0xFF;
try {
switch (packetId) {
case 3: {
processGetOtherSkin(data, sender, skinService);
break;
}
case 6: {
processGetOtherSkinByURL();
break;
}
default: {
throw new IOException("Unknown packet type " + packetId);
}
}
} catch (IOException ex) {
throw ex;
} catch (Throwable t) {
throw new IOException("Unhandled exception handling packet type " + packetId, t);
}
}
private static void processGetOtherSkin(final byte[] data, final ChannelHandlerContext sender, final SkinService skinService) throws IOException {
if (data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
final UUID searchUUID = bytesToUUID(data, 1);
skinService.processGetOtherSkin(searchUUID, sender);
}
private static void processGetOtherSkinByURL() throws IOException {
throw new IOException("Skin URLs not implemented");
}
public static void registerEaglerPlayer(final UUID clientUUID, final byte[] bs, final SkinService skinService) throws IOException {
if (bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int skinModel = -1;
final int packetType = bs[0] & 0xFF;
byte[] generatedPacket;
switch (packetType) {
case 1: {
if (bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset skin packet");
}
generatedPacket = makePresetResponse(clientUUID, bs[1] << 24 | bs[2] << 16 | bs[3] << 8 | (bs[4] & 0xFF));
break;
}
case 2: {
final byte[] pixels = new byte[16384];
if (bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChest(pixels, (byte) (-1));
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacket = makeCustomResponse(clientUUID, skinModel = (bs[1] & 0xFF), pixels);
break;
}
default: {
throw new IOException("Unknown skin packet type: " + packetType);
}
}
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket);
}
public static void registerEaglerPlayerFallback(final UUID clientUUID, final SkinService skinService) throws IOException {
final int skinModel = ((clientUUID.hashCode() & 0x1) != 0x0) ? 1 : 0;
final byte[] generatedPacket = makePresetResponse(clientUUID, skinModel);
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket);
}
public static void setAlphaForChest(final byte[] skin64x64, final byte alpha) {
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] = alpha;
}
}
}
public static byte[] makePresetResponse(final UUID uuid) {
return makePresetResponse(uuid, ((uuid.hashCode() & 0x1) != 0x0) ? 1 : 0);
}
public static byte[] makePresetResponse(final UUID uuid, final int presetId) {
final byte[] ret = new byte[21];
ret[0] = 4;
UUIDToBytes(uuid, ret, 1);
ret[17] = (byte) (presetId >> 24);
ret[18] = (byte) (presetId >> 16);
ret[19] = (byte) (presetId >> 8);
ret[20] = (byte) (presetId & 0xFF);
return ret;
}
public static byte[] makeCustomResponse(final UUID uuid, final int model, final byte[] pixels) {
final byte[] ret = new byte[18 + pixels.length];
ret[0] = 5;
UUIDToBytes(uuid, ret, 1);
ret[17] = (byte) model;
System.arraycopy(pixels, 0, ret, 18, pixels.length);
return ret;
}
public static UUID bytesToUUID(final byte[] bytes, final int off) {
final long msb = ((long) bytes[off] & 0xFFL) << 56 | ((long) bytes[off + 1] & 0xFFL) << 48 | ((long) bytes[off + 2] & 0xFFL) << 40 | ((long) bytes[off + 3] & 0xFFL) << 32 | ((long) bytes[off + 4] & 0xFFL) << 24 | ((long) bytes[off + 5] & 0xFFL) << 16 | ((long) bytes[off + 6] & 0xFFL) << 8 | ((long) bytes[off + 7] & 0xFFL);
final long lsb = ((long) bytes[off + 8] & 0xFFL) << 56 | ((long) bytes[off + 9] & 0xFFL) << 48 | ((long) bytes[off + 10] & 0xFFL) << 40 | ((long) bytes[off + 11] & 0xFFL) << 32 | ((long) bytes[off + 12] & 0xFFL) << 24 | ((long) bytes[off + 13] & 0xFFL) << 16 | ((long) bytes[off + 14] & 0xFFL) << 8 | ((long) bytes[off + 15] & 0xFFL);
return new UUID(msb, lsb);
}
public static void UUIDToBytes(final UUID uuid, final byte[] bytes, final int off) {
final long msb = uuid.getMostSignificantBits();
final long lsb = uuid.getLeastSignificantBits();
bytes[off] = (byte) (msb >> 56);
bytes[off + 1] = (byte) (msb >> 48);
bytes[off + 2] = (byte) (msb >> 40);
bytes[off + 3] = (byte) (msb >> 32);
bytes[off + 4] = (byte) (msb >> 24);
bytes[off + 5] = (byte) (msb >> 16);
bytes[off + 6] = (byte) (msb >> 8);
bytes[off + 7] = (byte) (msb & 0xFFL);
bytes[off + 8] = (byte) (lsb >> 56);
bytes[off + 9] = (byte) (lsb >> 48);
bytes[off + 10] = (byte) (lsb >> 40);
bytes[off + 11] = (byte) (lsb >> 32);
bytes[off + 12] = (byte) (lsb >> 24);
bytes[off + 13] = (byte) (lsb >> 16);
bytes[off + 14] = (byte) (lsb >> 8);
bytes[off + 15] = (byte) (lsb & 0xFFL);
}
}

View File

@ -0,0 +1,129 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import com.google.common.primitives.Ints;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.packet.PacketTypes;
import javax.imageio.ImageIO;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SkinService {
private final ConcurrentHashMap<UUID, CachedSkin> skinCache;
public SkinService() {
this.skinCache = new ConcurrentHashMap<>();
}
private static void sendData(final ChannelHandlerContext ctx, final byte[] data) {
final ByteBuf bb = ctx.alloc().buffer();
PacketTypes.writeVarInt(bb, MCPackets.S2C_PLUGIN_MESSAGE.getId((((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).version).getVersion()));
PacketTypes.writeString(bb, "EAG|Skins-1.8");
bb.writeBytes(data);
ctx.writeAndFlush(new BinaryWebSocketFrame(bb));
}
public void processGetOtherSkin(final UUID searchUUID, final ChannelHandlerContext sender) {
final CachedSkin cached = this.skinCache.get(searchUUID);
if (cached != null) {
sendData(sender, cached.packet);
} else if (EaglerSkinHandler.skinCollection.containsKey(searchUUID)) {
final byte[] src = EaglerSkinHandler.skinCollection.get(searchUUID);
byte[] res = new byte[src.length - 1];
System.arraycopy(src, 1, res, 0, res.length);
if (res.length == 8192) {
final int[] tmp1 = new int[2048];
final int[] tmp2 = new int[4096];
for (int i = 0; i < tmp1.length; ++i) {
tmp1[i] = Ints.fromBytes(res[i * 4 + 3], res[i * 4], res[i * 4 + 1], res[i * 4 + 2]);
}
SkinConverter.convert64x32to64x64(tmp1, tmp2);
res = new byte[16384];
for (int i = 0; i < tmp2.length; ++i) {
System.arraycopy(Ints.toByteArray(tmp2[i]), 0, res, i * 4, 4);
}
} else {
for (int j = 0; j < res.length; j += 4) {
final byte tmp3 = res[j + 3];
res[j + 3] = res[j + 2];
res[j + 2] = res[j + 1];
res[j + 1] = res[j];
res[j] = tmp3;
}
}
sendData(sender, SkinPackets.makeCustomResponse(searchUUID, 0, res));
} else {
sendData(sender, SkinPackets.makePresetResponse(searchUUID));
}
}
public void registerEaglercraftPlayer(final UUID clientUUID, final byte[] generatedPacket) throws IOException {
this.skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
EaglerSkinHandler.skinCollection.put(clientUUID, newToOldSkin(generatedPacket));
}
private static byte[] newToOldSkin(final byte[] packet) throws IOException {
final byte type = packet[0];
byte[] res;
switch (type) {
case 4: {
res = new byte[16385];
res[0] = 1;
final int presetId = packet[17] << 24 | packet[18] << 16 | packet[19] << 8 | packet[20];
final InputStream stream = Main.class.getResourceAsStream("/" + presetId + ".png");
if (stream == null) {
throw new IOException("Invalid skin preset: " + presetId);
}
System.arraycopy(((DataBufferByte) ImageIO.read(stream).getRaster().getDataBuffer()).getData(), 0, res, 1, 16384);
for (int i = 1; i < 16385; i += 4) {
final byte tmp = res[i];
res[i] = res[i + 1];
res[i + 1] = res[i + 2];
res[i + 2] = res[i + 3];
res[i + 3] = tmp;
}
break;
}
case 5: {
res = new byte[16385];
res[0] = 1;
System.arraycopy(packet, 18, res, 1, 16384);
for (int i = 1; i < 16385; i += 4) {
final byte tmp = res[i];
res[i] = res[i + 1];
res[i + 1] = res[i + 2];
res[i + 2] = res[i + 3];
res[i + 3] = tmp;
}
break;
}
default: {
throw new IOException("Invalid skin packet type: " + type);
}
}
return res;
}
public void unregisterPlayer(final UUID clientUUID) {
this.skinCache.remove(clientUUID);
EaglerSkinHandler.skinCollection.remove(clientUUID);
}
private static class CachedSkin {
protected final UUID uuid;
protected final byte[] packet;
protected CachedSkin(final UUID uuid, final byte[] packet) {
this.uuid = uuid;
this.packet = packet;
}
}
}

View File

@ -0,0 +1,23 @@
package me.ayunami2000.ayunViaProxyEagUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
public class WebSocketActiveNotifier extends ChannelInboundHandlerAdapter {
public void channelActive(final ChannelHandlerContext ctx) {
}
public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
ctx.fireChannelActive();
ctx.pipeline().remove(this);
}
super.userEventTriggered(ctx, evt);
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, null);
}
}

View File

@ -0,0 +1,4 @@
name: ViaProxyEagUtils
version: 1.0
author: ayunami2000
main: me.ayunami2000.ayunViaProxyEagUtils.Main