package lilypad.bukkit.connect.login;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelHandlerContext;
import lilypad.bukkit.connect.ConnectPlugin;
import lilypad.bukkit.connect.injector.NettyDecoderHandler;
import lilypad.bukkit.connect.injector.NettyInjectHandler;
import lilypad.bukkit.connect.injector.OfflineInjector;
import lilypad.bukkit.connect.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.UUID;
public class LoginNettyInjectHandler implements NettyInjectHandler {
private String requestedStateFieldCache;
private String serverHostFieldCache;
private Class<?> serverHostFieldClass;
private ConnectPlugin connectPlugin;
private LoginPayloadCache payloadCache;
public LoginNettyInjectHandler(ConnectPlugin connectPlugin, LoginPayloadCache payloadCache) {
this.connectPlugin = connectPlugin;
this.payloadCache = payloadCache;
}
public void packetReceived(NettyDecoderHandler handler, ChannelHandlerContext context, Object object) throws Exception {
String packetName = object.getClass().getSimpleName();
if (packetName.startsWith("PacketHandshakingInSetProtocol")) {
handleSetProtocol(context, object);
} else if (packetName.equals("PacketLoginInStart")) {
handlePacketLoginStart(context, object);
handler.disable();
}
}
private void handleSetProtocol(ChannelHandlerContext context, Object object) {
// Get requested state
try {
if (this.requestedStateFieldCache == null) {
for (Field field : object.getClass().getDeclaredFields()) {
if (!field.getType().getSimpleName().equals("EnumProtocol")) {
continue;
}
this.requestedStateFieldCache = field.getName();
break;
}
}
Object requestedStateEnum = ReflectionUtils.getPrivateField(object.getClass(), object, Object.class, this.requestedStateFieldCache);
if (requestedStateEnum.toString().equals("STATUS")) {
return;
}
} catch (Exception exception) {
exception.printStackTrace();
context.close();
return;
}
// Get server host
String serverHost;
try {
if (this.serverHostFieldCache == null) {
for (Field field : object.getClass().getSuperclass().getDeclaredFields()) {
if (!field.getType().getSimpleName().equals("String")) {
continue;
}
this.serverHostFieldCache = field.getName();
this.serverHostFieldClass = object.getClass().getSuperclass();
break;
}
if (this.serverHostFieldCache == null) {
for (Field field : object.getClass().getDeclaredFields()) {
if (!field.getType().getSimpleName().equals("String")) {
continue;
}
this.serverHostFieldCache = field.getName();
this.serverHostFieldClass = object.getClass();
break;
}
}
}
serverHost = ReflectionUtils.getPrivateField(this.serverHostFieldClass, object, String.class, this.serverHostFieldCache);
} catch (Exception exception) {
exception.printStackTrace();
context.close();
return;
}
// Get login payload
LoginPayload payload;
try {
payload = LoginPayload.decode(serverHost);
if (payload == null) {
throw new Exception(); // for lack of a better solution
}
} catch (Exception exception) {
context.close();
return;
}
// Check the security key
if (!payload.getSecurityKey().equals(this.connectPlugin.getSecurityKey())) {
// TODO tell the client the security key failed?
context.close();
return;
}
// Store the host
try {
ReflectionUtils.setFinalField(this.serverHostFieldClass, object, this.serverHostFieldCache, payload.getHost());
} catch (Exception exception) {
exception.printStackTrace();
}
// Store the real ip & port
try {
InetSocketAddress newRemoteAddress = new InetSocketAddress(payload.getRealIp(), payload.getRealPort());
// Netty
ReflectionUtils.setFinalField(AbstractChannel.class, context.channel(), "remoteAddress", newRemoteAddress);
// MC
Object networkManager = context.channel().pipeline().get("packet_handler");
if (ConnectPlugin.getProtocol().getGeneralVersion().equalsIgnoreCase("1.7")) {
String[] fields = ConnectPlugin.getProtocol().getLoginNettyInjectHandlerNetworkManager().split(",");
try {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, fields[0], newRemoteAddress);
} catch (Exception e) {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, fields[1], newRemoteAddress);
}
} else {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, ConnectPlugin.getProtocol().getLoginNettyInjectHandlerNetworkManager(), newRemoteAddress);
}
} catch (Exception exception) {
exception.printStackTrace();
}
// Submit to cache
this.payloadCache.submit(payload);
}
private void handlePacketLoginStart(ChannelHandlerContext context, Object object) {
// inject LoginListener
try {
Object networkManager = context.channel().pipeline().get("packet_handler");
try {
Class loginListenerProxyClass = LoginListenerProxy.get(networkManager);
Constructor loginListenerProxyConstructor = loginListenerProxyClass.getConstructors()[0];
Object offlineMinecraftServer = OfflineInjector.getOfflineMinecraftServer();
Object loginListenerProxy = loginListenerProxyConstructor.newInstance(offlineMinecraftServer, networkManager);
loginListenerProxyClass.getField("injectUuidCallback").set(loginListenerProxy, (Runnable) () -> {
try {
Field profileField = LoginListenerProxy.getProfileField();
GameProfile profile = (GameProfile) profileField.get(loginListenerProxy);
LoginPayload payload = payloadCache.getByName(profile.getName());
profile = new GameProfile(payload.getUUID(), profile.getName());
for (LoginPayload.Property payloadProperty : payload.getProperties()) {
Property property = new Property(payloadProperty.getName(), payloadProperty.getValue(), payloadProperty.getSignature());
profile.getProperties().put(payloadProperty.getName(), property);
}
profileField.set(loginListenerProxy, profile);
Field hostnameField = LoginListenerProxy.getLoginListenerClass().getField("hostname");
hostnameField.set(loginListenerProxy, payload.getHost());
} catch (Exception exception) {
exception.printStackTrace();
}
});
LoginListenerProxy.getPacketListenerField().set(networkManager, loginListenerProxy);
} catch(Exception exception) {
if (this.connectPlugin.getServer().getPluginManager().getPlugin("ProtocolSupport") == null) {
throw exception;
}
if (LoginListenerProxy.getPacketListenerField() == null) {
context.close();
return;
}
Object packetListener = LoginListenerProxy.getPacketListenerField().get(networkManager);
GameProfile profile = ReflectionUtils.getPrivateField(object.getClass(), object, GameProfile.class, "a");
LoginPayload payload = payloadCache.getByName(profile.getName());
LoginPayload.Property[] payloadProperties = payload.getProperties();
Property[] properties = new Property[payloadProperties.length];
for (int i = 0; i < properties.length; i++) {
LoginPayload.Property payloadProperty = payloadProperties[i];
Property property = new Property(payloadProperty.getName(), payloadProperty.getValue(), payloadProperty.getSignature());
properties[i] = property;
}
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, "spoofedUUID", payload.getUUID());
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, "spoofedProfile", properties);
try {
ReflectionUtils.setFinalField(packetListener.getClass().getSuperclass().getSuperclass(), packetListener, "isOnlineMode", false);
} catch(NoSuchFieldException exception1) {
ReflectionUtils.setFinalField(packetListener.getClass().getSuperclass(), packetListener, "isOnlineMode", false);
}
}
} catch (Exception exception) {
exception.printStackTrace();
context.close();
}
}
public boolean isEnabled() {
return this.connectPlugin.isEnabled();
}
}