Update #28 - Added capes, voice chat, FNAW skins, and fixes

This commit is contained in:
lax1dude
2024-04-20 16:20:06 -07:00
parent 42c57894f9
commit ba88b52022
171 changed files with 7476 additions and 868 deletions

View File

@ -140,4 +140,7 @@ public class GuiScreenAddRelay extends GuiScreen {
super.drawScreen(par1, par2, par3);
}
public boolean blockPTTKey() {
return this.serverName.isFocused() || this.serverAddress.isFocused();
}
}

View File

@ -117,7 +117,7 @@ public class GuiScreenLANConnecting extends GuiScreen {
this.mc.clearTitles();
networkManager.setConnectionState(EnumConnectionState.LOGIN);
networkManager.setNetHandler(new NetHandlerSingleplayerLogin(networkManager, mc, parent));
networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket()));
networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket(), EaglerProfile.getCapePacket()));
}
}
}

View File

@ -88,7 +88,7 @@ public class GuiScreenSingleplayerConnecting extends GuiScreen {
this.mc.clearTitles();
this.networkManager.setConnectionState(EnumConnectionState.LOGIN);
this.networkManager.setNetHandler(new NetHandlerSingleplayerLogin(this.networkManager, this.mc, this.menu));
this.networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket()));
this.networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket(), EaglerProfile.getCapePacket()));
}
try {
this.networkManager.processReceivedPackets();

View File

@ -195,4 +195,8 @@ public class GuiShareToLan extends GuiScreen {
super.updateScreen();
this.codeTextField.updateCursorCounter();
}
public boolean blockPTTKey() {
return this.codeTextField.isFocused();
}
}

View File

@ -361,6 +361,9 @@ public class EaglerIntegratedServerWorker {
case IPCPacket17ConfigureLAN.ID: {
IPCPacket17ConfigureLAN pkt = (IPCPacket17ConfigureLAN)ipc;
if(!pkt.iceServers.isEmpty() && ServerPlatformSingleplayer.getClientConfigAdapter().isAllowVoiceClient()) {
currentProcess.enableVoice(pkt.iceServers.toArray(new String[pkt.iceServers.size()]));
}
currentProcess.getConfigurationManager().configureLAN(pkt.gamemode, pkt.cheats); // don't use iceServers
break;

View File

@ -16,7 +16,9 @@ import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.WorldServer;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldSettings.GameType;
import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedCapeService;
import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedSkinService;
import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService;
/**
* Copyright (c) 2023-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -45,6 +47,8 @@ public class EaglerMinecraftServer extends MinecraftServer {
protected boolean paused;
protected EaglerSaveHandler saveHandler;
protected IntegratedSkinService skinService;
protected IntegratedCapeService capeService;
protected IntegratedVoiceService voiceService;
private long lastTPSUpdate = 0l;
@ -62,6 +66,8 @@ public class EaglerMinecraftServer extends MinecraftServer {
Bootstrap.register();
this.saveHandler = new EaglerSaveHandler(savesDir, world);
this.skinService = new IntegratedSkinService(new VFile2(saveHandler.getWorldDirectory(), "eagler/skulls"));
this.capeService = new IntegratedCapeService();
this.voiceService = null;
this.setServerOwner(owner);
logger.info("server owner: " + owner);
this.setDemo(demo);
@ -76,6 +82,27 @@ public class EaglerMinecraftServer extends MinecraftServer {
return skinService;
}
public IntegratedCapeService getCapeService() {
return capeService;
}
public IntegratedVoiceService getVoiceService() {
return voiceService;
}
public void enableVoice(String[] iceServers) {
if(iceServers != null) {
if(voiceService != null) {
voiceService.changeICEServers(iceServers);
}else {
voiceService = new IntegratedVoiceService(iceServers);
for(EntityPlayerMP player : getConfigurationManager().func_181057_v()) {
voiceService.handlePlayerLoggedIn(player);
}
}
}
}
public void setBaseServerProperties(EnumDifficulty difficulty, GameType gamemode) {
this.difficulty = difficulty;
this.gamemode = gamemode;

View File

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

View File

@ -0,0 +1,110 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.minecraft.entity.player.EntityPlayerMP;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class IntegratedCapePackets {
public static final int PACKET_MY_CAPE_PRESET = 0x01;
public static final int PACKET_MY_CAPE_CUSTOM = 0x02;
public static final int PACKET_GET_OTHER_CAPE = 0x03;
public static final int PACKET_OTHER_CAPE_PRESET = 0x04;
public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05;
public static void processPacket(byte[] data, EntityPlayerMP sender, IntegratedCapeService capeService) throws IOException {
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int packetId = (int)data[0] & 0xFF;
try {
switch(packetId) {
case PACKET_GET_OTHER_CAPE:
processGetOtherCape(data, sender, capeService);
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 processGetOtherCape(byte[] data, EntityPlayerMP sender, IntegratedCapeService capeService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
EaglercraftUUID searchUUID = IntegratedSkinPackets.bytesToUUID(data, 1);
capeService.processGetOtherCape(searchUUID, sender);
}
public static void registerEaglerPlayer(EaglercraftUUID clientUUID, byte[] bs, IntegratedCapeService capeService) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
byte[] generatedPacket;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
case PACKET_MY_CAPE_PRESET:
if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset cape packet");
}
generatedPacket = IntegratedCapePackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break;
case PACKET_MY_CAPE_CUSTOM:
if(bs.length != 1174) {
throw new IOException("Invalid length " + bs.length + " for custom cape packet");
}
generatedPacket = IntegratedCapePackets.makeCustomResponse(clientUUID, bs, 1, 1173);
break;
default:
throw new IOException("Unknown skin packet type: " + packetType);
}
capeService.registerEaglercraftPlayer(clientUUID, generatedPacket);
}
public static void registerEaglerPlayerFallback(EaglercraftUUID clientUUID, IntegratedCapeService capeService) {
capeService.registerEaglercraftPlayer(clientUUID, IntegratedCapePackets.makePresetResponse(clientUUID, 0));
}
public static byte[] makePresetResponse(EaglercraftUUID uuid, int presetId) {
byte[] ret = new byte[1 + 16 + 4];
ret[0] = (byte)PACKET_OTHER_CAPE_PRESET;
IntegratedSkinPackets.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(EaglercraftUUID uuid, byte[] pixels) {
return makeCustomResponse(uuid, pixels, 0, pixels.length);
}
public static byte[] makeCustomResponse(EaglercraftUUID uuid, byte[] pixels, int offset, int length) {
byte[] ret = new byte[1 + 16 + length];
ret[0] = (byte)PACKET_OTHER_CAPE_CUSTOM;
IntegratedSkinPackets.UUIDToBytes(uuid, ret, 1);
System.arraycopy(pixels, offset, ret, 17, length);
return ret;
}
}

View File

@ -0,0 +1,77 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.skins;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class IntegratedCapeService {
public static final Logger logger = LogManager.getLogger("IntegratedCapeService");
public static final int masterRateLimitPerPlayer = 250;
public static final String CHANNEL = "EAG|Capes-1.8";
private final Map<EaglercraftUUID, byte[]> capesCache = new HashMap();
public void processPacket(byte[] packetData, EntityPlayerMP sender) {
try {
IntegratedCapePackets.processPacket(packetData, sender, this);
} catch (IOException e) {
logger.error("Invalid skin request packet recieved from player {}!", sender.getName());
logger.error(e);
sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin request packet recieved!");
}
}
public void processLoginPacket(byte[] packetData, EntityPlayerMP sender) {
try {
IntegratedCapePackets.registerEaglerPlayer(sender.getUniqueID(), packetData, this);
} catch (IOException e) {
logger.error("Invalid skin data packet recieved from player {}!", sender.getName());
logger.error(e);
sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin data packet recieved!");
}
}
public void registerEaglercraftPlayer(EaglercraftUUID playerUUID, byte[] capePacket) {
capesCache.put(playerUUID, capePacket);
}
public void processGetOtherCape(EaglercraftUUID searchUUID, EntityPlayerMP sender) {
byte[] maybeCape = capesCache.get(searchUUID);
if(maybeCape == null) {
maybeCape = IntegratedCapePackets.makePresetResponse(searchUUID, 0);
}
sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer(Unpooled.buffer(maybeCape, maybeCape.length).writerIndex(maybeCape.length))));
}
public void unregisterPlayer(EaglercraftUUID playerUUID) {
synchronized(capesCache) {
capesCache.remove(playerUUID);
}
}
}

View File

@ -0,0 +1,255 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.voice;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.voice.ExpiringSet;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class IntegratedVoiceService {
public static final Logger logger = LogManager.getLogger("IntegratedVoiceService");
public static final String CHANNEL = "EAG|Voice-1.8";
private byte[] iceServersPacket;
private final Map<EaglercraftUUID, EntityPlayerMP> voicePlayers = new HashMap<>();
private final Map<EaglercraftUUID, ExpiringSet<EaglercraftUUID>> voiceRequests = new HashMap<>();
private final Set<VoicePair> voicePairs = new HashSet<>();
public IntegratedVoiceService(String[] iceServers) {
iceServersPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers);
}
public void changeICEServers(String[] iceServers) {
iceServersPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers);
}
private static class VoicePair {
private final EaglercraftUUID uuid1;
private final EaglercraftUUID uuid2;
@Override
public int hashCode() {
return uuid1.hashCode() ^ uuid2.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VoicePair other = (VoicePair) obj;
return (uuid1.equals(other.uuid1) && uuid2.equals(other.uuid2))
|| (uuid1.equals(other.uuid2) && uuid2.equals(other.uuid1));
}
private VoicePair(EaglercraftUUID uuid1, EaglercraftUUID uuid2) {
this.uuid1 = uuid1;
this.uuid2 = uuid2;
}
private boolean anyEquals(EaglercraftUUID uuid) {
return uuid1.equals(uuid) || uuid2.equals(uuid);
}
}
public void handlePlayerLoggedIn(EntityPlayerMP player) {
player.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer(
Unpooled.buffer(iceServersPacket, iceServersPacket.length).writerIndex(iceServersPacket.length))));
}
public void handlePlayerLoggedOut(EntityPlayerMP player) {
removeUser(player.getUniqueID());
}
public void processPacket(PacketBuffer packetData, EntityPlayerMP sender) {
try {
IntegratedVoiceSignalPackets.processPacket(packetData, sender, this);
} catch (IOException e) {
logger.error("Invalid voice signal packet recieved from player {}!", sender.getName());
logger.error(e);
sender.playerNetServerHandler.kickPlayerFromServer("Invalid voice signal packet recieved!");
}
}
void handleVoiceSignalPacketTypeRequest(EaglercraftUUID player, EntityPlayerMP sender) {
EaglercraftUUID senderUUID = sender.getUniqueID();
if (senderUUID.equals(player))
return; // prevent duplicates
if (!voicePlayers.containsKey(senderUUID))
return;
EntityPlayerMP targetPlayerCon = voicePlayers.get(player);
if (targetPlayerCon == null)
return;
VoicePair newPair = new VoicePair(player, senderUUID);
if (voicePairs.contains(newPair))
return; // already paired
ExpiringSet<EaglercraftUUID> senderRequestSet = voiceRequests.get(senderUUID);
if (senderRequestSet == null) {
voiceRequests.put(senderUUID, senderRequestSet = new ExpiringSet<>(2000));
}
if (!senderRequestSet.add(player)) {
return;
}
// check if other has requested earlier
ExpiringSet<EaglercraftUUID> theSet;
if ((theSet = voiceRequests.get(player)) != null && theSet.contains(senderUUID)) {
theSet.remove(senderUUID);
if (theSet.isEmpty())
voiceRequests.remove(player);
senderRequestSet.remove(player);
if (senderRequestSet.isEmpty())
voiceRequests.remove(senderUUID);
// send each other add data
voicePairs.add(newPair);
targetPlayerCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
IntegratedVoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false)));
sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
IntegratedVoiceSignalPackets.makeVoiceSignalPacketConnect(player, true)));
}
}
void handleVoiceSignalPacketTypeConnect(EntityPlayerMP sender) {
if (voicePlayers.containsKey(sender.getUniqueID())) {
return;
}
boolean hasNoOtherPlayers = voicePlayers.isEmpty();
voicePlayers.put(sender.getUniqueID(), sender);
if (hasNoOtherPlayers) {
return;
}
byte[] packetToBroadcast = IntegratedVoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (EntityPlayerMP userCon : voicePlayers.values()) {
userCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer(Unpooled
.buffer(packetToBroadcast, packetToBroadcast.length).writerIndex(packetToBroadcast.length))));
}
}
void handleVoiceSignalPacketTypeICE(EaglercraftUUID player, String str, EntityPlayerMP sender) {
VoicePair pair = new VoicePair(player, sender.getUniqueID());
EntityPlayerMP pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
if (pass != null) {
pass.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
IntegratedVoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueID(), str)));
}
}
void handleVoiceSignalPacketTypeDesc(EaglercraftUUID player, String str, EntityPlayerMP sender) {
VoicePair pair = new VoicePair(player, sender.getUniqueID());
EntityPlayerMP pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
if (pass != null) {
pass.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
IntegratedVoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueID(), str)));
}
}
void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID player, EntityPlayerMP sender) {
if (player != null) {
if (!voicePlayers.containsKey(player)) {
return;
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
EaglercraftUUID target = null;
if (voicePair.uuid1.equals(player)) {
target = voicePair.uuid2;
} else if (voicePair.uuid2.equals(player)) {
target = voicePair.uuid1;
}
if (target != null) {
pairsItr.remove();
EntityPlayerMP conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnect(player);
}
conn.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
new PacketBuffer(Unpooled.buffer(userDisconnectPacket, userDisconnectPacket.length)
.writerIndex(userDisconnectPacket.length))));
}
sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnectPB(target)));
}
}
} else {
removeUser(sender.getUniqueID());
}
}
public void removeUser(EaglercraftUUID user) {
if (voicePlayers.remove(user) == null) {
return;
}
voiceRequests.remove(user);
if (voicePlayers.size() > 0) {
byte[] voicePlayersPkt = IntegratedVoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (EntityPlayerMP userCon : voicePlayers.values()) {
if (!user.equals(userCon.getUniqueID())) {
userCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
new PacketBuffer(Unpooled.buffer(voicePlayersPkt, voicePlayersPkt.length)
.writerIndex(voicePlayersPkt.length))));
}
}
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
EaglercraftUUID target = null;
if (voicePair.uuid1.equals(user)) {
target = voicePair.uuid2;
} else if (voicePair.uuid2.equals(user)) {
target = voicePair.uuid1;
}
if (target != null) {
pairsItr.remove();
if (voicePlayers.size() > 0) {
EntityPlayerMP conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnect(user);
}
conn.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL,
new PacketBuffer(Unpooled.buffer(userDisconnectPacket, userDisconnectPacket.length)
.writerIndex(userDisconnectPacket.length))));
}
}
}
}
}
}

View File

@ -0,0 +1,198 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.voice;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.PacketBuffer;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class IntegratedVoiceSignalPackets {
static final int VOICE_SIGNAL_ALLOWED = 0;
static final int VOICE_SIGNAL_REQUEST = 0;
static final int VOICE_SIGNAL_CONNECT = 1;
static final int VOICE_SIGNAL_DISCONNECT = 2;
static final int VOICE_SIGNAL_ICE = 3;
static final int VOICE_SIGNAL_DESC = 4;
static final int VOICE_SIGNAL_GLOBAL = 5;
public static void processPacket(PacketBuffer buffer, EntityPlayerMP sender, IntegratedVoiceService voiceService) throws IOException {
int packetId = -1;
if(buffer.readableBytes() == 0) {
throw new IOException("Zero-length packet recieved");
}
try {
packetId = buffer.readUnsignedByte();
switch(packetId) {
case VOICE_SIGNAL_REQUEST: {
voiceService.handleVoiceSignalPacketTypeRequest(buffer.readUuid(), sender);
break;
}
case VOICE_SIGNAL_CONNECT: {
voiceService.handleVoiceSignalPacketTypeConnect(sender);
break;
}
case VOICE_SIGNAL_ICE: {
voiceService.handleVoiceSignalPacketTypeICE(buffer.readUuid(), buffer.readStringFromBuffer(32767), sender);
break;
}
case VOICE_SIGNAL_DESC: {
voiceService.handleVoiceSignalPacketTypeDesc(buffer.readUuid(), buffer.readStringFromBuffer(32767), sender);
break;
}
case VOICE_SIGNAL_DISCONNECT: {
voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? buffer.readUuid() : null, sender);
break;
}
default: {
throw new IOException("Unknown packet type " + packetId);
}
}
if(buffer.readableBytes() > 0) {
throw new IOException("Voice packet is too long!");
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling voice packet type " + packetId, t);
}
}
static byte[] makeVoiceSignalPacketAllowed(boolean allowed, String[] iceServers) {
if (iceServers == null) {
byte[] ret = new byte[2];
ByteBuf wrappedBuffer = Unpooled.buffer(ret, ret.length);
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
return ret;
}
byte[][] iceServersBytes = new byte[iceServers.length][];
int totalLen = 2 + PacketBuffer.getVarIntSize(iceServers.length);
for(int i = 0; i < iceServers.length; ++i) {
byte[] b = iceServersBytes[i] = iceServers[i].getBytes(StandardCharsets.UTF_8);
totalLen += PacketBuffer.getVarIntSize(b.length) + b.length;
}
byte[] ret = new byte[totalLen];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
wrappedBuffer.writeVarIntToBuffer(iceServersBytes.length);
for(int i = 0; i < iceServersBytes.length; ++i) {
byte[] b = iceServersBytes[i];
wrappedBuffer.writeVarIntToBuffer(b.length);
wrappedBuffer.writeBytes(b);
}
return ret;
}
static byte[] makeVoiceSignalPacketGlobal(Collection<EntityPlayerMP> users) {
int cnt = users.size();
byte[][] displayNames = new byte[cnt][];
int i = 0;
for(EntityPlayerMP user : users) {
String name = user.getName();
if(name.length() > 16) name = name.substring(0, 16);
displayNames[i++] = name.getBytes(StandardCharsets.UTF_8);
}
int totalLength = 1 + PacketBuffer.getVarIntSize(cnt) + (cnt << 4);
for(i = 0; i < cnt; ++i) {
totalLength += PacketBuffer.getVarIntSize(displayNames[i].length) + displayNames[i].length;
}
byte[] ret = new byte[totalLength];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_GLOBAL);
wrappedBuffer.writeVarIntToBuffer(cnt);
for(EntityPlayerMP user : users) {
wrappedBuffer.writeUuid(user.getUniqueID());
}
for(i = 0; i < cnt; ++i) {
wrappedBuffer.writeVarIntToBuffer(displayNames[i].length);
wrappedBuffer.writeBytes(displayNames[i]);
}
return ret;
}
static PacketBuffer makeVoiceSignalPacketConnect(EaglercraftUUID player, boolean offer) {
byte[] ret = new byte[18];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
wrappedBuffer.writeUuid(player);
wrappedBuffer.writeBoolean(offer);
return wrappedBuffer;
}
static byte[] makeVoiceSignalPacketConnectAnnounce(EaglercraftUUID player) {
byte[] ret = new byte[17];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
wrappedBuffer.writeUuid(player);
return ret;
}
static byte[] makeVoiceSignalPacketDisconnect(EaglercraftUUID player) {
if(player == null) {
return new byte[] { (byte)VOICE_SIGNAL_DISCONNECT };
}
byte[] ret = new byte[17];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT);
wrappedBuffer.writeUuid(player);
return ret;
}
static PacketBuffer makeVoiceSignalPacketDisconnectPB(EaglercraftUUID player) {
if(player == null) {
byte[] ret = new byte[1];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT);
return wrappedBuffer;
}
byte[] ret = new byte[17];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT);
wrappedBuffer.writeUuid(player);
return wrappedBuffer;
}
static PacketBuffer makeVoiceSignalPacketICE(EaglercraftUUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + PacketBuffer.getVarIntSize(strBytes.length) + strBytes.length];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_ICE);
wrappedBuffer.writeUuid(player);
wrappedBuffer.writeVarIntToBuffer(strBytes.length);
wrappedBuffer.writeBytes(strBytes);
return wrappedBuffer;
}
static PacketBuffer makeVoiceSignalPacketDesc(EaglercraftUUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + PacketBuffer.getVarIntSize(strBytes.length) + strBytes.length];
PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length));
wrappedBuffer.writeByte(VOICE_SIGNAL_DESC);
wrappedBuffer.writeUuid(player);
wrappedBuffer.writeVarIntToBuffer(strBytes.length);
wrappedBuffer.writeBytes(strBytes);
return wrappedBuffer;
}
}