Skip to content
This repository has been archived by the owner on Dec 17, 2023. It is now read-only.

Combine WebSocket function #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = co

minecraft {
mappings channel: 'snapshot', version: "20210215-1.16.3"
accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg')
runs {
client {
workingDirectory project.file('run')
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/com/ixnah/mc/protocol/CustomProtocol.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.ixnah.mc.protocol;

import com.ixnah.mc.websocket.WebSocketConsumer;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;

import java.net.URI;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 16:19
*/
public class CustomProtocol {

public static final AttributeKey<Boolean> HANDSHAKE_COMPLETED_KEY = AttributeKey.valueOf("CustomProtocolHandshakeCompleted");
public static final AttributeKey<URI> SERVER_URI_KEY = AttributeKey.valueOf("CustomProtocolServerUri");
public static final String URI_REGEX = "[a-zA-z]+://[^\\s]*";

private static final Map<String, Class<? extends Consumer<Channel>>> protocolMap = new ConcurrentHashMap<>(16);
private static final Map<String, Integer> defaultPortMap = new ConcurrentHashMap<>(16);

public static void accept(String name, Channel channel) {
Consumer<Channel> channelConsumer = (ch) -> { };
try {
Class<? extends Consumer<Channel>> channelConsumerClass = protocolMap.get(name);
if (channelConsumerClass != null) {
channelConsumer = channelConsumerClass.newInstance();
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
channelConsumer.accept(channel);
}

public static void register(Class<? extends Consumer<Channel>> channelConsumer, String... names) {
for (String name : names) {
protocolMap.put(name, channelConsumer);
}
}

public static Class<? extends Consumer<Channel>> unregister(String name) {
return protocolMap.remove(name);
}

public static void setDefaultPort(int port, String... names) {
for (String name : names) {
defaultPortMap.put(name, port);
}
}

public static int getServerUriPort(URI serverUri) {
int uriPort = serverUri.getPort();
if (uriPort != -1) {
return uriPort;
}
String name = serverUri.getScheme();
if (name != null) {
name = name.toLowerCase(Locale.ROOT);
}
return defaultPortMap.getOrDefault(name, 25565);
}

static {
register(WebSocketConsumer.class, "ws", "wss", "http", "https");
setDefaultPort(80, "ws", "http");
setDefaultPort(443, "wss", "https");
setDefaultPort(25565, "tcp", "mc", "minecraft");
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/ixnah/mc/protocol/UriServerAddress.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ixnah.mc.protocol;

import net.minecraft.client.multiplayer.ServerAddress;

import java.net.URI;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 10:55
*/
public class UriServerAddress extends ServerAddress {

private final URI address;

public UriServerAddress(URI address) {
super(address.getHost(), CustomProtocol.getServerUriPort(address));
this.address = address.normalize();
}

public URI getAddress() {
return address;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ixnah.mc.protocol.bridge;

import java.net.URI;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 12:05
*/
public interface CustomProtocolBridge {
void setServerUri(URI uri);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.ixnah.mc.protocol.mixin.client.gui.screen;

import com.ixnah.mc.protocol.UriServerAddress;
import com.ixnah.mc.protocol.bridge.CustomProtocolBridge;
import net.minecraft.client.gui.DialogTexts;
import net.minecraft.client.gui.screen.ConnectingScreen;
import net.minecraft.client.gui.screen.DisconnectedScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.multiplayer.ServerAddress;
import net.minecraft.client.network.login.ClientLoginNetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.ProtocolType;
import net.minecraft.network.handshake.client.CHandshakePacket;
import net.minecraft.network.login.client.CLoginStartPacket;
import net.minecraft.util.DefaultUncaughtExceptionHandler;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicInteger;

import static com.ixnah.mc.protocol.CustomProtocol.URI_REGEX;
import static java.util.Objects.requireNonNull;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 22:47
*/
@Mixin(ConnectingScreen.class)
public abstract class ConnectingScreenMixin extends Screen {
@Shadow
@Final
private static final AtomicInteger CONNECTION_ID = new AtomicInteger(0);
@Shadow
@Final
private static final Logger LOGGER = LogManager.getLogger();
@Shadow
private NetworkManager networkManager;
@Shadow
private boolean cancel;
@Shadow
@Final
private Screen previousGuiScreen;

private URI serverUri;

@Shadow
private void func_209514_a(ITextComponent p_209514_1_) {
}

protected ConnectingScreenMixin(ITextComponent titleIn) {
super(titleIn);
}

@Redirect(method = "<init>(Lnet/minecraft/client/gui/screen/Screen;Lnet/minecraft/client/Minecraft;Lnet/minecraft/client/multiplayer/ServerData;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ServerAddress;getIP()Ljava/lang/String;"))
private String getServerIP(ServerAddress serverAddress) {
if (serverAddress instanceof UriServerAddress) {
return ((UriServerAddress) serverAddress).getAddress().toString();
}
return serverAddress.getIP();
}

@Redirect(method = "<init>*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ConnectingScreen;connect(Ljava/lang/String;I)V"))
private void redirectConnect(ConnectingScreen connectingScreen, String ip, int port) {
if (ip.matches(URI_REGEX)) {
try {
UriServerAddress uriServerAddress = new UriServerAddress(new URI(ip));
serverUri = uriServerAddress.getAddress();
this.connect(uriServerAddress.getIP(), uriServerAddress.getPort());
return;
} catch (URISyntaxException ignored) {
}
}
this.connect(ip, port);
}

/**
* @author 寒兮
* @reason Mixin不支持非static class
* static class无法访问外部类的变量
*/
@Overwrite
private void connect(final String ip, final int port) {
requireNonNull(this.minecraft, "Minecraft must not be null!");
LOGGER.info("Connecting to {}, {}", ip, port);
Thread thread = new Thread(() -> {
InetAddress inetaddress = null;

try {
if (this.cancel) {
return;
}

inetaddress = InetAddress.getByName(ip);
this.networkManager = NetworkManager.createNetworkManagerAndConnect(inetaddress, port, this.minecraft.gameSettings.isUsingNativeTransport());
((CustomProtocolBridge) this.networkManager).setServerUri(serverUri);
this.networkManager.setNetHandler(new ClientLoginNetHandler(this.networkManager, this.minecraft, this.previousGuiScreen, this::func_209514_a));
this.networkManager.sendPacket(new CHandshakePacket(ip, port, ProtocolType.LOGIN));
this.networkManager.sendPacket(new CLoginStartPacket(this.minecraft.getSession().getProfile()));
} catch (UnknownHostException unknownhostexception) {
if (this.cancel) {
return;
}

LOGGER.error("Couldn't connect to server", unknownhostexception);
this.minecraft.execute(() -> this.minecraft.displayGuiScreen(new DisconnectedScreen(this.previousGuiScreen, DialogTexts.CONNECTION_FAILED, new TranslationTextComponent("disconnect.genericReason", "Unknown host"))));
} catch (Exception exception) {
if (this.cancel) {
return;
}

LOGGER.error("Couldn't connect to server", exception);
String s = inetaddress == null ? exception.toString() : exception.toString().replaceAll(inetaddress + ":" + port, "");
this.minecraft.execute(() -> this.minecraft.displayGuiScreen(new DisconnectedScreen(this.previousGuiScreen, DialogTexts.CONNECTION_FAILED, new TranslationTextComponent("disconnect.genericReason", s))));
}

}, "Server Connector #" + CONNECTION_ID.incrementAndGet());
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
thread.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ixnah.mc.protocol.mixin.client.multiplayer;

import com.ixnah.mc.protocol.UriServerAddress;
import net.minecraft.client.multiplayer.ServerAddress;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.net.URI;

import static com.ixnah.mc.protocol.CustomProtocol.URI_REGEX;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 10:53
*/
@Mixin(ServerAddress.class)
public class ServerAddressMixin {

@Inject(method = "fromString", cancellable = true, at = @At("HEAD"))
private static void parseCustomProtocol(String addrString, CallbackInfoReturnable<ServerAddress> cir) {
try {
if (addrString.matches(URI_REGEX))
cir.setReturnValue(new UriServerAddress(new URI(addrString.trim())));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ixnah.mc.protocol.mixin.client.network;

import com.ixnah.mc.protocol.UriServerAddress;
import com.ixnah.mc.protocol.bridge.CustomProtocolBridge;
import net.minecraft.client.multiplayer.ServerAddress;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.network.ServerPinger;
import net.minecraft.network.NetworkManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 8:44
*/
@Mixin(ServerPinger.class)
public class ServerPingerMixin {

@Inject(method = "ping", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/network/NetworkManager;createNetworkManagerAndConnect(Ljava/net/InetAddress;IZ)Lnet/minecraft/network/NetworkManager;"))
private void connectCustomProtocol(ServerData server, Runnable p_147224_2_, CallbackInfo ci, ServerAddress serveraddress, NetworkManager networkmanager) {
if (serveraddress instanceof UriServerAddress) {
UriServerAddress uriServerAddress = (UriServerAddress) serveraddress;
((CustomProtocolBridge) networkmanager).setServerUri(uriServerAddress.getAddress());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.ixnah.mc.protocol.mixin.network;

import com.ixnah.mc.protocol.CustomProtocol;
import com.ixnah.mc.protocol.bridge.CustomProtocolBridge;
import io.netty.channel.Channel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.network.IPacket;
import net.minecraft.network.NetworkManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.net.URI;
import java.util.Locale;

import static com.ixnah.mc.protocol.util.SpinUtil.spinRequireNonNull;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/12 11:56
*/
@Mixin(NetworkManager.class)
public class NetworkManagerMixin implements CustomProtocolBridge {
@Shadow private Channel channel;

@Inject(method = "dispatchPacket", at = @At("HEAD"))
public void onDispatchPacket(IPacket<?> inPacket, GenericFutureListener<? extends Future<? super Void>> futureListeners, CallbackInfo ci) {
Boolean handshakeCompleted = channel.attr(CustomProtocol.HANDSHAKE_COMPLETED_KEY).get();
URI serverUri = channel.attr(CustomProtocol.SERVER_URI_KEY).get();
if (serverUri != null && (handshakeCompleted == null || !handshakeCompleted)) {
CustomProtocol.accept(serverUri.getScheme().toLowerCase(Locale.ROOT), channel);
}
channel.attr(CustomProtocol.HANDSHAKE_COMPLETED_KEY).set(true);
}

@Override
public void setServerUri(URI uri) {
spinRequireNonNull(this, "channel", "channel can't be null!");
channel.attr(CustomProtocol.SERVER_URI_KEY).set(uri);
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/ixnah/mc/protocol/util/SpinUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.ixnah.mc.protocol.util;

import net.minecraftforge.fml.unsafe.UnsafeHacks;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

import static java.util.Objects.requireNonNull;

/**
* @author 寒兮
* @version 1.0
* @date 2021/7/19 15:21
*/
public class SpinUtil {

private SpinUtil() {
}

public static <T> T spinRequireNonNull(Object object, String fieldName, String message) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
AtomicInteger count = new AtomicInteger(0);
T result;
while ((result = UnsafeHacks.getField(field, object)) == null && count.incrementAndGet() < 1000) {
LockSupport.parkNanos("SpinRequireNonNull", 1000L); // 获取时可能为null, 自旋等待
}
return requireNonNull(result, "channel can't be null!");
} catch (NoSuchFieldException e) {
throw new NullPointerException(message);
}
}
}
Loading