mirror of
https://github.com/ayunami2000/ayunViaProxyEagUtils.git
synced 2024-12-21 14:24:10 -08:00
open sauce
This commit is contained in:
commit
0dba9a05e6
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
.gradle
|
||||
.idea
|
||||
build
|
||||
gradle
|
||||
libs
|
||||
gradlew
|
||||
gradlew.bat
|
||||
src/main/resources/*.png
|
29
LICENSE
Normal file
29
LICENSE
Normal 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
4
README.md
Normal 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
14
build.gradle
Normal 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
2
settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
rootProject.name = 'ayunViaProxyEagUtils'
|
||||
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
67
src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java
Normal file
67
src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
4
src/main/resources/viaproxy.yml
Normal file
4
src/main/resources/viaproxy.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: ViaProxyEagUtils
|
||||
version: 1.0
|
||||
author: ayunami2000
|
||||
main: me.ayunami2000.ayunViaProxyEagUtils.Main
|
Loading…
Reference in New Issue
Block a user