Compare commits
No commits in common. "main" and "main" have entirely different histories.
24 changed files with 325 additions and 623 deletions
|
@ -4,6 +4,9 @@
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
<option name="delegatedBuild" value="true" />
|
||||||
|
<option name="testRunner" value="GRADLE" />
|
||||||
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
|
|
23
README.md
23
README.md
|
@ -1,23 +0,0 @@
|
||||||
# [e4mc_minecraft](https://e4mc.link)
|
|
||||||
|
|
||||||
[![Modrinth Downloads](https://img.shields.io/modrinth/dt/qANg5Jrr?color=%2300af5c&logo=modrinth&style=for-the-badge)](https://modrinth.com/project/qANg5Jrr)
|
|
||||||
[![Modrinth Followers](https://img.shields.io/modrinth/followers/qANg5Jrr?color=00af5c&logo=modrinth&style=for-the-badge)](https://modrinth.com/project/qANg5Jrr)
|
|
||||||
[![CurseForge Sucks](https://img.shields.io/badge/cuseforge-sucks-f16436?style=for-the-badge)](https://curseforge.com/minecraft/mc-mods/e4mc)
|
|
||||||
|
|
||||||
Open a LAN server to anyone, anywhere, anytime.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
[Modrinth](https://modrinth.com/project/qANg5Jrr)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Open to LAN as normal
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Please contribute
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
|
@ -5,8 +5,8 @@ plugins {
|
||||||
preprocess {
|
preprocess {
|
||||||
val fabric11904 = createNode("1.19.4-fabric", 11903, "yarn")
|
val fabric11904 = createNode("1.19.4-fabric", 11903, "yarn")
|
||||||
val fabric11802 = createNode("1.18.2-fabric", 11802, "yarn")
|
val fabric11802 = createNode("1.18.2-fabric", 11802, "yarn")
|
||||||
val forge11904 = createNode("1.19.4-forge", 11903, "srg")
|
val forge11904 = createNode("1.19.4-forge", 11903, "yarn")
|
||||||
val forge11802 = createNode("1.18.2-forge", 11802, "srg")
|
val forge11802 = createNode("1.18.2-forge", 11802, "yarn")
|
||||||
|
|
||||||
fabric11904.link(forge11904)
|
fabric11904.link(forge11904)
|
||||||
forge11904.link(forge11802)
|
forge11904.link(forge11802)
|
||||||
|
|
|
@ -3,5 +3,5 @@ org.gradle.jvmargs=-Xmx2G
|
||||||
|
|
||||||
mod.name=e4mc
|
mod.name=e4mc
|
||||||
mod.id=e4mc_minecraft
|
mod.id=e4mc_minecraft
|
||||||
mod.version=4.0.1
|
mod.version=3.0.0
|
||||||
mod.group=vg.skye
|
mod.group=vg.skye
|
0
gradlew
vendored
Executable file → Normal file
0
gradlew
vendored
Executable file → Normal file
|
@ -10,6 +10,7 @@ pluginManagement {
|
||||||
maven("https://repo.essential.gg/repository/maven-public")
|
maven("https://repo.essential.gg/repository/maven-public")
|
||||||
maven("https://server.bbkr.space/artifactory/libs-release/")
|
maven("https://server.bbkr.space/artifactory/libs-release/")
|
||||||
maven("https://jitpack.io/")
|
maven("https://jitpack.io/")
|
||||||
|
|
||||||
// Snapshots
|
// Snapshots
|
||||||
maven("https://maven.deftu.xyz/snapshots")
|
maven("https://maven.deftu.xyz/snapshots")
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
@ -24,7 +25,7 @@ pluginManagement {
|
||||||
kotlin("jvm") version(kotlin)
|
kotlin("jvm") version(kotlin)
|
||||||
kotlin("plugin.serialization") version(kotlin)
|
kotlin("plugin.serialization") version(kotlin)
|
||||||
|
|
||||||
val epgt = "1.17.1"
|
val epgt = "1.10.3"
|
||||||
id("xyz.deftu.gradle.multiversion-root") version(epgt)
|
id("xyz.deftu.gradle.multiversion-root") version(epgt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,16 @@ import io.netty.bootstrap.ServerBootstrap;
|
||||||
import io.netty.channel.ServerChannel;
|
import io.netty.channel.ServerChannel;
|
||||||
import io.netty.channel.local.LocalAddress;
|
import io.netty.channel.local.LocalAddress;
|
||||||
import io.netty.channel.local.LocalServerChannel;
|
import io.netty.channel.local.LocalServerChannel;
|
||||||
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
import net.minecraft.server.ServerNetworkIo;
|
import net.minecraft.server.ServerNetworkIo;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
import vg.skye.e4mc_minecraft.E4mcClient;
|
import vg.skye.e4mc_minecraft.E4mcClient;
|
||||||
import vg.skye.e4mc_minecraft.QuiclimeHandler;
|
import vg.skye.e4mc_minecraft.E4mcRelayHandler;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -39,15 +39,15 @@ public abstract class ServerNetworkIoMixin {
|
||||||
initializingE4mc.set(false);
|
initializingE4mc.set(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QuiclimeHandler handler = new QuiclimeHandler();
|
E4mcRelayHandler handler = new E4mcRelayHandler();
|
||||||
E4mcClient.HANDLER = handler;
|
E4mcClient.HANDLER = handler;
|
||||||
handler.startAsync();
|
handler.connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ModifyArg(method = "bind", at = @At(value = "INVOKE", target = "Lio/netty/bootstrap/ServerBootstrap;channel(Ljava/lang/Class;)Lio/netty/bootstrap/AbstractBootstrap;", remap = false))
|
@Redirect(method = "bind", at = @At(value = "INVOKE", target = "Lio/netty/bootstrap/ServerBootstrap;channel(Ljava/lang/Class;)Lio/netty/bootstrap/AbstractBootstrap;", remap = false))
|
||||||
private Class<? extends ServerChannel> redirectChannel(Class<? extends ServerChannel> aClass) {
|
private AbstractBootstrap<ServerBootstrap, ServerChannel> redirectChannel(ServerBootstrap instance, Class<? extends ServerSocketChannel> aClass) {
|
||||||
return initializingE4mc.get() ? LocalServerChannel.class : aClass;
|
return initializingE4mc.get() ? instance.channel(LocalServerChannel.class) : instance.channel(aClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Redirect(method = "bind", at = @At(value = "INVOKE", target = "Lio/netty/bootstrap/ServerBootstrap;localAddress(Ljava/net/InetAddress;I)Lio/netty/bootstrap/AbstractBootstrap;", remap = false))
|
@Redirect(method = "bind", at = @At(value = "INVOKE", target = "Lio/netty/bootstrap/ServerBootstrap;localAddress(Ljava/net/InetAddress;I)Lio/netty/bootstrap/AbstractBootstrap;", remap = false))
|
||||||
|
@ -58,7 +58,7 @@ public abstract class ServerNetworkIoMixin {
|
||||||
@Inject(method = "stop", at = @At("HEAD"))
|
@Inject(method = "stop", at = @At("HEAD"))
|
||||||
private void stop(CallbackInfo ci) {
|
private void stop(CallbackInfo ci) {
|
||||||
if (E4mcClient.HANDLER != null) {
|
if (E4mcClient.HANDLER != null) {
|
||||||
E4mcClient.HANDLER.stop();
|
E4mcClient.HANDLER.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
package vg.skye.e4mc_minecraft
|
|
||||||
|
|
||||||
//#if FABRIC==1
|
|
||||||
import net.fabricmc.api.EnvType
|
|
||||||
import net.fabricmc.loader.api.FabricLoader
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraftforge.fml.loading.FMLLoader
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient
|
|
||||||
import net.minecraft.text.ClickEvent
|
|
||||||
import net.minecraft.text.HoverEvent
|
|
||||||
import net.minecraft.text.Text
|
|
||||||
|
|
||||||
//#if MC>=11900
|
|
||||||
import net.minecraft.util.Formatting
|
|
||||||
//#elseif FABRIC==1
|
|
||||||
//$$ import net.minecraft.text.TranslatableText
|
|
||||||
//$$ import net.minecraft.text.LiteralText
|
|
||||||
//$$ import net.minecraft.util.Formatting
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraft.network.chat.TranslatableComponent
|
|
||||||
//$$ import net.minecraft.network.chat.TextComponent
|
|
||||||
//$$ import net.minecraft.ChatFormatting
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
object ChatHelper {
|
|
||||||
//#if FABRIC==1
|
|
||||||
val isClient = FabricLoader.getInstance().environmentType.equals(EnvType.CLIENT)
|
|
||||||
val isServer = FabricLoader.getInstance().environmentType.equals(EnvType.SERVER)
|
|
||||||
//#else
|
|
||||||
//$$ val isClient = FMLLoader.getDist().isClient
|
|
||||||
//$$ val isServer = FMLLoader.getDist().isDedicatedServer
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
fun sendLiteral(msg: String) {
|
|
||||||
alertUser(createLiteralMessage("[e4mc] $msg"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendError() {
|
|
||||||
if (E4mcClient.HANDLER?.state == QuiclimeHandlerState.STARTED) {
|
|
||||||
E4mcClient.HANDLER?.state = QuiclimeHandlerState.UNHEALTHY
|
|
||||||
}
|
|
||||||
alertUser(createTranslatableMessage("text.e4mc_minecraft.error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendDomainAssignment(domain: String) {
|
|
||||||
if (isServer) {
|
|
||||||
E4mcClient.LOGGER.warn("e4mc running on Dedicated Server; This works, but isn't recommended as e4mc is designed for short-lived LAN servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
E4mcClient.LOGGER.info("Domain assigned: $domain")
|
|
||||||
alertUser(createDomainAssignedMessage(domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun alertUser(message: Text) {
|
|
||||||
if (isClient) {
|
|
||||||
MinecraftClient.getInstance().inGameHud.chatHud.addMessage(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createLiteralMessage(message: String): Text {
|
|
||||||
//#if MC>=11900
|
|
||||||
return Text.literal(message)
|
|
||||||
//#elseif FABRIC==1
|
|
||||||
//$$ return LiteralText(message)
|
|
||||||
//#else
|
|
||||||
//$$ return TextComponent(message)
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createTranslatableMessage(key: String, vararg objects: Any?): Text {
|
|
||||||
//#if MC>=11900
|
|
||||||
return Text.translatable(key, *objects)
|
|
||||||
//#elseif FABRIC==1
|
|
||||||
//$$ return TranslatableText(key, *objects)
|
|
||||||
//#else
|
|
||||||
//$$ return TranslatableComponent(key, *objects)
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createDomainAssignedMessage(domain: String): Text {
|
|
||||||
//#if MC>=11900
|
|
||||||
return Text.translatable(
|
|
||||||
"text.e4mc_minecraft.domainAssigned",
|
|
||||||
Text.literal(domain).styled {
|
|
||||||
it
|
|
||||||
.withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
|
||||||
.withColor(Formatting.GREEN)
|
|
||||||
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("chat.copy.click")))
|
|
||||||
}
|
|
||||||
).append(
|
|
||||||
Text.translatable("text.e4mc_minecraft.clickToStop").styled {
|
|
||||||
it
|
|
||||||
.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
|
||||||
.withColor(Formatting.GRAY)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
//#elseif FABRIC==1
|
|
||||||
//$$ return TranslatableText("text.e4mc_minecraft.domainAssigned", LiteralText(domain).styled {
|
|
||||||
//$$ return@styled it
|
|
||||||
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
|
||||||
//$$ .withColor(Formatting.GREEN)
|
|
||||||
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableText("chat.copy.click")))
|
|
||||||
//$$ }).append(
|
|
||||||
//$$ TranslatableText("text.e4mc_minecraft.clickToStop").styled {
|
|
||||||
//$$ return@styled it
|
|
||||||
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
|
||||||
//$$ .withColor(Formatting.GRAY)
|
|
||||||
//$$ }
|
|
||||||
//$$ )
|
|
||||||
//#else
|
|
||||||
//$$ return TranslatableComponent("text.e4mc_minecraft.domainAssigned", TextComponent(domain).withStyle {
|
|
||||||
//$$ return@withStyle it
|
|
||||||
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
|
||||||
//$$ .withColor(ChatFormatting.GREEN)
|
|
||||||
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableComponent("chat.copy.click")))
|
|
||||||
//$$ }).append(
|
|
||||||
//$$ TranslatableComponent("text.e4mc_minecraft.clickToStop").withStyle {
|
|
||||||
//$$ return@withStyle it
|
|
||||||
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
|
||||||
//$$ .withColor(ChatFormatting.GRAY)
|
|
||||||
//$$ }
|
|
||||||
//$$ )
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package vg.skye.e4mc_minecraft
|
|
||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher
|
|
||||||
import net.minecraft.server.command.CommandManager
|
|
||||||
import net.minecraft.server.command.ServerCommandSource
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity
|
|
||||||
import net.minecraft.text.Text
|
|
||||||
|
|
||||||
object CommandsHelper {
|
|
||||||
private fun getPlayerFromSource(src: ServerCommandSource): ServerPlayerEntity? {
|
|
||||||
return try {
|
|
||||||
src.playerOrThrow
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun registerCommandWithDispatcher(dispatcher: CommandDispatcher<ServerCommandSource>) {
|
|
||||||
dispatcher.register(
|
|
||||||
CommandManager.literal("e4mc")
|
|
||||||
.requires { src ->
|
|
||||||
if (src.server.isDedicated) {
|
|
||||||
src.hasPermissionLevel(4)
|
|
||||||
} else {
|
|
||||||
src.server.isHost((getPlayerFromSource(src) ?: return@requires false).gameProfile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.then(
|
|
||||||
CommandManager.literal("stop")
|
|
||||||
.executes { context ->
|
|
||||||
if ((E4mcClient.HANDLER != null) && (E4mcClient.HANDLER?.state != QuiclimeHandlerState.STOPPED)) {
|
|
||||||
E4mcClient.HANDLER!!.stop()
|
|
||||||
sendMessageToSource(ChatHelper.createTranslatableMessage("text.e4mc_minecraft.closeServer"), context.source)
|
|
||||||
} else {
|
|
||||||
sendErrorToSource(ChatHelper.createTranslatableMessage("text.e4mc_minecraft.serverAlreadyClosed"), context.source)
|
|
||||||
}
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("restart")
|
|
||||||
.executes {
|
|
||||||
if ((E4mcClient.HANDLER != null) && (E4mcClient.HANDLER?.state != QuiclimeHandlerState.STARTED)) {
|
|
||||||
E4mcClient.HANDLER?.stop()
|
|
||||||
val handler = QuiclimeHandler()
|
|
||||||
E4mcClient.HANDLER = handler
|
|
||||||
handler.startAsync()
|
|
||||||
}
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendMessageToSource(text: Text, source: ServerCommandSource) {
|
|
||||||
source.sendFeedback(text, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendErrorToSource(text: Text, source: ServerCommandSource) {
|
|
||||||
source.sendError(text)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,11 +33,11 @@ object E4mcClient : ModInitializer {
|
||||||
//#endif
|
//#endif
|
||||||
const val NAME = "e4mc"
|
const val NAME = "e4mc"
|
||||||
const val ID = "e4mc_minecraft"
|
const val ID = "e4mc_minecraft"
|
||||||
const val VERSION = "4.0.1"
|
const val VERSION = "3.0.0"
|
||||||
@JvmField
|
@JvmField
|
||||||
val LOGGER: Logger = LoggerFactory.getLogger("e4mc")
|
val LOGGER: Logger = LoggerFactory.getLogger("e4mc")
|
||||||
@JvmField
|
@JvmField
|
||||||
var HANDLER: QuiclimeHandler? = null
|
var HANDLER: E4mcRelayHandler? = null
|
||||||
|
|
||||||
//#if FABRIC==1
|
//#if FABRIC==1
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
|
@ -46,14 +46,56 @@ object E4mcClient : ModInitializer {
|
||||||
//#else
|
//#else
|
||||||
//$$ CommandRegistrationCallback.EVENT.register { dispatcher, _ ->
|
//$$ CommandRegistrationCallback.EVENT.register { dispatcher, _ ->
|
||||||
//#endif
|
//#endif
|
||||||
CommandsHelper.registerCommandWithDispatcher(dispatcher)
|
dispatcher.register(literal("e4mc")
|
||||||
|
.then(
|
||||||
|
literal("stop")
|
||||||
|
.executes { context ->
|
||||||
|
if (HANDLER != null) {
|
||||||
|
HANDLER!!.close()
|
||||||
|
HANDLER = null
|
||||||
|
//#if MC>=11904
|
||||||
|
context.source.sendMessage(Text.translatable("text.e4mc_minecraft.closeServer"))
|
||||||
|
//#else
|
||||||
|
//$$ context.source.sendFeedback(TranslatableText("text.e4mc_minecraft.closeServer"), false)
|
||||||
|
//#endif
|
||||||
|
} else {
|
||||||
|
//#if MC>=11904
|
||||||
|
context.source.sendMessage(Text.translatable("text.e4mc_minecraft.serverAlreadyClosed"))
|
||||||
|
//#else
|
||||||
|
//$$ context.source.sendFeedback(TranslatableText("text.e4mc_minecraft.serverAlreadyClosed"), false)
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#else
|
//#else
|
||||||
//$$ @SubscribeEvent
|
//$$ @SubscribeEvent
|
||||||
//$$ fun onRegisterCommandEvent(event: RegisterCommandsEvent) {
|
//$$ fun onRegisterCommandEvent(event: RegisterCommandsEvent) {
|
||||||
//$$ val dispatcher = event.getDispatcher()
|
//$$ val commandDispatcher = event.getDispatcher()
|
||||||
//$$ CommandsHelper.registerCommandWithDispatcher(dispatcher)
|
//$$ commandDispatcher.register(literal("e4mc")
|
||||||
|
//$$ .then(
|
||||||
|
//$$ literal("stop")
|
||||||
|
//$$ .executes { context ->
|
||||||
|
//$$ if (HANDLER != null) {
|
||||||
|
//$$ HANDLER!!.close()
|
||||||
|
//$$ HANDLER = null
|
||||||
|
//$$ //#if MC>=11904
|
||||||
|
//$$ context.source.sendSuccess(Component.translatable("text.e4mc_minecraft.closeServer"), false)
|
||||||
|
//$$ //#else
|
||||||
|
//$$ //$$ context.source.sendSuccess(TranslatableComponent("text.e4mc_minecraft.closeServer"), false)
|
||||||
|
//$$ //#endif
|
||||||
|
//$$ } else {
|
||||||
|
//$$ //#if MC>=11904
|
||||||
|
//$$ context.source.sendFailure(Component.translatable("text.e4mc_minecraft.serverAlreadyClosed"))
|
||||||
|
//$$ //#else
|
||||||
|
//$$ //$$ context.source.sendFailure(TranslatableComponent("text.e4mc_minecraft.serverAlreadyClosed"))
|
||||||
|
//$$ //#endif
|
||||||
|
//$$ }
|
||||||
|
//$$ 1
|
||||||
|
//$$ }
|
||||||
|
//$$ ))
|
||||||
//$$ }
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
}
|
}
|
||||||
|
|
235
src/main/kotlin/vg/skye/e4mc_minecraft/E4mcRelayHandler.kt
Normal file
235
src/main/kotlin/vg/skye/e4mc_minecraft/E4mcRelayHandler.kt
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
package vg.skye.e4mc_minecraft
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import io.netty.bootstrap.Bootstrap
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import io.netty.channel.*
|
||||||
|
import io.netty.channel.local.LocalAddress
|
||||||
|
import io.netty.channel.local.LocalChannel
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup
|
||||||
|
//#if FABRIC==1
|
||||||
|
import net.fabricmc.api.EnvType
|
||||||
|
import net.fabricmc.loader.api.FabricLoader
|
||||||
|
//#else
|
||||||
|
//$$ import net.minecraftforge.fml.loading.FMLLoader
|
||||||
|
//#endif
|
||||||
|
import net.minecraft.client.MinecraftClient
|
||||||
|
import net.minecraft.text.ClickEvent
|
||||||
|
import net.minecraft.text.HoverEvent
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
//#if MC>=11900
|
||||||
|
import net.minecraft.util.Formatting
|
||||||
|
//#elseif FABRIC==1
|
||||||
|
//$$ import net.minecraft.text.TranslatableText
|
||||||
|
//$$ import net.minecraft.text.LiteralText
|
||||||
|
//$$ import net.minecraft.util.Formatting
|
||||||
|
//#else
|
||||||
|
//$$ import net.minecraft.network.chat.TranslatableComponent
|
||||||
|
//$$ import net.minecraft.network.chat.TextComponent
|
||||||
|
//$$ import net.minecraft.ChatFormatting
|
||||||
|
//#endif
|
||||||
|
import org.java_websocket.client.WebSocketClient
|
||||||
|
import org.java_websocket.handshake.ServerHandshake
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
|
|
||||||
|
|
||||||
|
data class DomainAssignedMessage(val DomainAssigned: String)
|
||||||
|
data class ChannelOpenMessage(val ChannelOpen: List<Any>)
|
||||||
|
data class ChannelClosedMessage(val ChannelClosed: Number)
|
||||||
|
|
||||||
|
class E4mcRelayHandler: WebSocketClient(URI("wss://ingress.e4mc.link")) {
|
||||||
|
private val gson = Gson()
|
||||||
|
private val childChannels = mutableMapOf<Int, LocalChannel>()
|
||||||
|
private val messageQueue = mutableMapOf<Int, ArrayBlockingQueue<ByteBuffer>>()
|
||||||
|
private val eventLoopGroup = NioEventLoopGroup()
|
||||||
|
|
||||||
|
override fun onOpen(handshakedata: ServerHandshake?) {
|
||||||
|
// not much to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(message: String?) {
|
||||||
|
E4mcClient.LOGGER.info("WebSocket Text message: {}", message)
|
||||||
|
val json = gson.fromJson(message, JsonObject::class.java)
|
||||||
|
when {
|
||||||
|
json.has("DomainAssigned") -> handleDomainAssigned(json)
|
||||||
|
json.has("ChannelOpen") -> handleChannelOpen(json)
|
||||||
|
json.has("ChannelClosed") -> handleChannelClosed(json)
|
||||||
|
else -> E4mcClient.LOGGER.warn("Unhandled WebSocket Text message: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(bytes: ByteBuffer) {
|
||||||
|
val channelId = bytes.get()
|
||||||
|
val rest = bytes.slice()
|
||||||
|
val channel = childChannels[channelId.toInt()]
|
||||||
|
if (channel == null) {
|
||||||
|
if (messageQueue[channelId.toInt()] == null) {
|
||||||
|
E4mcClient.LOGGER.info("Creating queue for channel: {}", channelId)
|
||||||
|
messageQueue[channelId.toInt()] = ArrayBlockingQueue(8)
|
||||||
|
}
|
||||||
|
messageQueue[channelId.toInt()]!!.add(rest)
|
||||||
|
} else {
|
||||||
|
val byteBuf = channel.alloc().buffer(rest.remaining())
|
||||||
|
byteBuf.writeBytes(rest)
|
||||||
|
channel.writeAndFlush(byteBuf).sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClose(code: Int, reason: String?, remote: Boolean) {
|
||||||
|
childChannels.forEach { (_, channel) -> channel.close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(ex: java.lang.Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDomainAssigned(json: JsonObject) {
|
||||||
|
val msg = gson.fromJson(json, DomainAssignedMessage::class.java)
|
||||||
|
|
||||||
|
//#if FABRIC==1
|
||||||
|
val isClient = FabricLoader.getInstance().environmentType.equals(EnvType.CLIENT)
|
||||||
|
val isServer = FabricLoader.getInstance().environmentType.equals(EnvType.SERVER)
|
||||||
|
//#else
|
||||||
|
//$$ val isClient = FMLLoader.getDist().isClient
|
||||||
|
//$$ val isServer = FMLLoader.getDist().isDedicatedServer
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
if (isServer) {
|
||||||
|
E4mcClient.LOGGER.warn("e4mc running on Dedicated Server; This works, but isn't recommended as e4mc is designed for short-lived LAN servers")
|
||||||
|
}
|
||||||
|
E4mcClient.LOGGER.info("Domain assigned: ${msg.DomainAssigned}")
|
||||||
|
|
||||||
|
if (isClient) {
|
||||||
|
alertUser(msg.DomainAssigned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun alertUser(domain: String) {
|
||||||
|
try {
|
||||||
|
MinecraftClient.getInstance().inGameHud.chatHud.addMessage(
|
||||||
|
createMessage(domain)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMessage(domain: String): Text {
|
||||||
|
//#if MC>=11900
|
||||||
|
return Text.translatable(
|
||||||
|
"text.e4mc_minecraft.domainAssigned",
|
||||||
|
Text.literal(domain).styled {
|
||||||
|
it
|
||||||
|
.withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
||||||
|
.withColor(Formatting.GREEN)
|
||||||
|
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("chat.copy.click")))
|
||||||
|
}
|
||||||
|
).append(
|
||||||
|
Text.translatable("text.e4mc_minecraft.clickToStop").styled {
|
||||||
|
it
|
||||||
|
.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
||||||
|
.withColor(Formatting.GRAY)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//#elseif FABRIC==1
|
||||||
|
//$$ return TranslatableText("text.e4mc_minecraft.domainAssigned", LiteralText(domain).styled {
|
||||||
|
//$$ return@styled it
|
||||||
|
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
||||||
|
//$$ .withColor(Formatting.GREEN)
|
||||||
|
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableText("chat.copy.click")))
|
||||||
|
//$$ }).append(
|
||||||
|
//$$ TranslatableText("text.e4mc_minecraft.clickToStop").styled {
|
||||||
|
//$$ return@styled it
|
||||||
|
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
||||||
|
//$$ .withColor(Formatting.GRAY)
|
||||||
|
//$$ }
|
||||||
|
//$$ )
|
||||||
|
//#else
|
||||||
|
//$$ return TranslatableComponent("text.e4mc_minecraft.domainAssigned", TextComponent(domain).withStyle {
|
||||||
|
//$$ return@withStyle it
|
||||||
|
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
|
||||||
|
//$$ .withColor(ChatFormatting.GREEN)
|
||||||
|
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableComponent("chat.copy.click")))
|
||||||
|
//$$ }).append(
|
||||||
|
//$$ TranslatableComponent("text.e4mc_minecraft.clickToStop").withStyle {
|
||||||
|
//$$ return@withStyle it
|
||||||
|
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
|
||||||
|
//$$ .withColor(ChatFormatting.GRAY)
|
||||||
|
//$$ }
|
||||||
|
//$$ )
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleChannelOpen(json: JsonObject) {
|
||||||
|
val msg = gson.fromJson(json, ChannelOpenMessage::class.java)
|
||||||
|
val channelId = msg.ChannelOpen[0] as Number
|
||||||
|
val clientInfo = msg.ChannelOpen[1] as String
|
||||||
|
E4mcClient.LOGGER.info("Channel opened: channelId=$channelId, clientInfo=$clientInfo")
|
||||||
|
val host = clientInfo.substringBeforeLast(':')
|
||||||
|
val port = clientInfo.substringAfterLast(':').toInt()
|
||||||
|
val addr = InetSocketAddress(host, port)
|
||||||
|
|
||||||
|
val self = this
|
||||||
|
|
||||||
|
Bootstrap()
|
||||||
|
.channel(LocalChannel::class.java)
|
||||||
|
.handler(object : ChannelInitializer<LocalChannel>() {
|
||||||
|
@Throws(java.lang.Exception::class)
|
||||||
|
override fun initChannel(ch: LocalChannel) {
|
||||||
|
ch.pipeline().addLast(ChildHandler(self, addr, channelId.toInt()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.group(eventLoopGroup)
|
||||||
|
.connect(LocalAddress("e4mc-relay"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleChannelClosed(json: JsonObject) {
|
||||||
|
val msg = gson.fromJson(json, ChannelClosedMessage::class.java)
|
||||||
|
val channelId = msg.ChannelClosed.toInt()
|
||||||
|
E4mcClient.LOGGER.info("Closing channel as requested: {}", channelId)
|
||||||
|
childChannels.remove(channelId)?.let {
|
||||||
|
it.pipeline().get(ChildHandler::class.java).isClosedFromServer = true
|
||||||
|
it.close()
|
||||||
|
E4mcClient.LOGGER.info("Channel closed: channelId=$channelId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildHandler(private val parent: E4mcRelayHandler, private val address: InetSocketAddress, private val channelId: Int): SimpleChannelInboundHandler<ByteBuf>() {
|
||||||
|
var isClosedFromServer = false
|
||||||
|
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||||
|
// ctx.writeAndFlush(address)
|
||||||
|
if (parent.messageQueue[channelId] != null) {
|
||||||
|
parent.messageQueue[channelId]!!.forEach { buf ->
|
||||||
|
run {
|
||||||
|
E4mcClient.LOGGER.info("Handling queued buffer: {}", buf)
|
||||||
|
val byteBuf = ctx.alloc().buffer(buf.remaining())
|
||||||
|
byteBuf.writeBytes(buf)
|
||||||
|
ctx.writeAndFlush(byteBuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.messageQueue.remove(channelId)
|
||||||
|
}
|
||||||
|
parent.childChannels[channelId] = ctx.channel() as LocalChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||||
|
val buf = ByteArray(msg.readableBytes() + 1)
|
||||||
|
buf[0] = channelId.toByte()
|
||||||
|
msg.readBytes(buf, 1, msg.readableBytes())
|
||||||
|
parent.send(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||||
|
E4mcClient.LOGGER.info("Channel closed: {}", channelId)
|
||||||
|
if (!isClosedFromServer) {
|
||||||
|
parent.send(parent.gson.toJson(ChannelClosedMessage(channelId)))
|
||||||
|
}
|
||||||
|
parent.childChannels.remove(channelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,320 +0,0 @@
|
||||||
package vg.skye.e4mc_minecraft
|
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import io.netty.bootstrap.Bootstrap
|
|
||||||
import io.netty.buffer.ByteBuf
|
|
||||||
import io.netty.buffer.Unpooled
|
|
||||||
import io.netty.channel.*
|
|
||||||
import io.netty.channel.ChannelHandler.Sharable
|
|
||||||
import io.netty.channel.local.LocalAddress
|
|
||||||
import io.netty.channel.local.LocalChannel
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup
|
|
||||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete
|
|
||||||
import io.netty.channel.socket.nio.NioDatagramChannel
|
|
||||||
import io.netty.handler.codec.ByteToMessageCodec
|
|
||||||
import io.netty.incubator.codec.quic.*
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.net.Inet4Address
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.net.URI
|
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
interface QuiclimeControlMessage
|
|
||||||
|
|
||||||
data class RequestDomainAssignmentMessageServerbound(val kind: String = "request_domain_assignment"): QuiclimeControlMessage
|
|
||||||
|
|
||||||
data class DomainAssignmentCompleteMessageClientbound(val kind: String = "domain_assignment_complete", val domain: String): QuiclimeControlMessage
|
|
||||||
data class RequestMessageBroadcastMessageClientbound(val kind: String = "request_message_broadcast", val message: String): QuiclimeControlMessage
|
|
||||||
|
|
||||||
class QuiclimeControlMessageCodec : ByteToMessageCodec<QuiclimeControlMessage>() {
|
|
||||||
val gson = Gson()
|
|
||||||
|
|
||||||
override fun encode(ctx: ChannelHandlerContext, msg: QuiclimeControlMessage, out: ByteBuf) {
|
|
||||||
val json = gson.toJson(msg).toByteArray()
|
|
||||||
out.writeByte(json.size)
|
|
||||||
out.writeBytes(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun decode(ctx: ChannelHandlerContext, `in`: ByteBuf, out: MutableList<Any>) {
|
|
||||||
val size = `in`.getByte(`in`.readerIndex()).toInt()
|
|
||||||
if (`in`.readableBytes() >= size + 1) {
|
|
||||||
`in`.skipBytes(1)
|
|
||||||
val buf = ByteArray(size)
|
|
||||||
`in`.readBytes(buf)
|
|
||||||
val json = gson.fromJson(buf.decodeToString(), JsonObject::class.java)
|
|
||||||
out.add(gson.fromJson(json, when (json["kind"].asString) {
|
|
||||||
"domain_assignment_complete" -> DomainAssignmentCompleteMessageClientbound::class.java
|
|
||||||
"request_message_broadcast" -> RequestMessageBroadcastMessageClientbound::class.java
|
|
||||||
else -> throw Exception("Invalid message type")
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuiclimeToMinecraftHandler(private val toQuiclime: QuicStreamChannel) : ChannelInboundHandlerAdapter() {
|
|
||||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
|
||||||
ctx.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
|
||||||
toQuiclime.writeAndFlush(msg).addListener {
|
|
||||||
if (it.isSuccess) {
|
|
||||||
ctx.channel().read()
|
|
||||||
} else {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
toQuiclime.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
|
||||||
QuiclimeHandler.LOGGER.info("channel inactive(from MC): {} (MC: {})", toQuiclime, ctx.channel())
|
|
||||||
if (toQuiclime.isActive) {
|
|
||||||
toQuiclime.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("OVERRIDE_DEPRECATION")
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
|
|
||||||
super.exceptionCaught(ctx, cause)
|
|
||||||
ChatHelper.sendError()
|
|
||||||
this.channelInactive(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuiclimeToQuiclimeHandler : ChannelInboundHandlerAdapter() {
|
|
||||||
private var toMinecraft: LocalChannel? = null
|
|
||||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
|
||||||
QuiclimeHandler.LOGGER.info("channel active: {}", ctx.channel())
|
|
||||||
val fut = Bootstrap()
|
|
||||||
.group(ctx.channel().eventLoop())
|
|
||||||
.channel(LocalChannel::class.java)
|
|
||||||
.handler(QuiclimeToMinecraftHandler(ctx.channel() as QuicStreamChannel))
|
|
||||||
.option(ChannelOption.AUTO_READ, false)
|
|
||||||
.connect(LocalAddress("e4mc-relay"))
|
|
||||||
toMinecraft = fut.channel() as LocalChannel
|
|
||||||
fut.addListener {
|
|
||||||
if (it.isSuccess) {
|
|
||||||
ctx.channel().read()
|
|
||||||
} else {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
ctx.channel().close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
|
||||||
if (toMinecraft?.isActive == true) {
|
|
||||||
toMinecraft!!.writeAndFlush(msg).addListener {
|
|
||||||
if (it.isSuccess) {
|
|
||||||
ctx.channel().read();
|
|
||||||
} else {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
(it as ChannelFuture).channel().close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
|
||||||
QuiclimeHandler.LOGGER.info("channel inactive(from Quiclime): {} (MC: {})", ctx.channel(), toMinecraft)
|
|
||||||
if (toMinecraft?.isActive == true) {
|
|
||||||
toMinecraft!!.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
|
||||||
if (evt === ChannelInputShutdownReadComplete.INSTANCE) {
|
|
||||||
this.channelInactive(ctx)
|
|
||||||
}
|
|
||||||
super.userEventTriggered(ctx, evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("OVERRIDE_DEPRECATION")
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
|
|
||||||
super.exceptionCaught(ctx, cause)
|
|
||||||
ChatHelper.sendError()
|
|
||||||
this.channelInactive(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class BrokerResponse(val id: String, val host: String, val port: Int)
|
|
||||||
|
|
||||||
enum class QuiclimeHandlerState {
|
|
||||||
STARTING,
|
|
||||||
STARTED,
|
|
||||||
UNHEALTHY,
|
|
||||||
STOPPING,
|
|
||||||
STOPPED
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuiclimeHandler {
|
|
||||||
private val group = NioEventLoopGroup()
|
|
||||||
private var datagramChannel: NioDatagramChannel? = null
|
|
||||||
private var quicChannel: QuicChannel? = null
|
|
||||||
|
|
||||||
var state: QuiclimeHandlerState = QuiclimeHandlerState.STARTING
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val LOGGER: Logger = LoggerFactory.getLogger("e4mc-quiclime")
|
|
||||||
fun startAsync() {
|
|
||||||
thread(start = true) {
|
|
||||||
E4mcClient.HANDLER = QuiclimeHandler()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startAsync() {
|
|
||||||
thread(start = true) {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
try {
|
|
||||||
val httpClient = HttpClient.newHttpClient()
|
|
||||||
val request = HttpRequest
|
|
||||||
.newBuilder(URI("https://broker.e4mc.link/getBestRelay"))
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.build()
|
|
||||||
LOGGER.info("req: {}", request)
|
|
||||||
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
|
|
||||||
LOGGER.info("resp: {}", response)
|
|
||||||
if (response.statusCode() != 200) {
|
|
||||||
throw Exception()
|
|
||||||
}
|
|
||||||
val relayInfo = Gson().fromJson(response.body(), BrokerResponse::class.java)
|
|
||||||
LOGGER.info("using relay {}", relayInfo.id)
|
|
||||||
val context: QuicSslContext = QuicSslContextBuilder
|
|
||||||
.forClient()
|
|
||||||
.applicationProtocols("quiclime")
|
|
||||||
.build()
|
|
||||||
val codec = QuicClientCodecBuilder()
|
|
||||||
.sslContext(context)
|
|
||||||
.sslEngineProvider {
|
|
||||||
context.newEngine(it.alloc(), relayInfo.host, relayInfo.port)
|
|
||||||
}
|
|
||||||
.initialMaxStreamsBidirectional(512)
|
|
||||||
.maxIdleTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.initialMaxData(4611686018427387903)
|
|
||||||
.initialMaxStreamDataBidirectionalRemote(1250000)
|
|
||||||
.initialMaxStreamDataBidirectionalLocal(1250000)
|
|
||||||
.initialMaxStreamDataUnidirectional(1250000)
|
|
||||||
.build()
|
|
||||||
Bootstrap()
|
|
||||||
.group(group)
|
|
||||||
.channel(NioDatagramChannel::class.java)
|
|
||||||
.handler(codec)
|
|
||||||
.bind(0)
|
|
||||||
.addListener { datagramChannelFuture ->
|
|
||||||
if (!datagramChannelFuture.isSuccess) {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
throw datagramChannelFuture.cause()
|
|
||||||
}
|
|
||||||
datagramChannel = (datagramChannelFuture as ChannelFuture).channel() as NioDatagramChannel
|
|
||||||
QuicChannel.newBootstrap(datagramChannel)
|
|
||||||
.streamHandler(
|
|
||||||
@Sharable
|
|
||||||
object : ChannelInitializer<QuicStreamChannel>() {
|
|
||||||
override fun initChannel(ch: QuicStreamChannel) {
|
|
||||||
ch.pipeline().addLast(QuiclimeToQuiclimeHandler())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.handler(object : ChannelInboundHandlerAdapter() {
|
|
||||||
@Suppress("OVERRIDE_DEPRECATION")
|
|
||||||
override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
|
|
||||||
super.exceptionCaught(ctx, cause)
|
|
||||||
ChatHelper.sendError()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
|
||||||
super.channelInactive(ctx)
|
|
||||||
state = QuiclimeHandlerState.STOPPED
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.streamOption(ChannelOption.AUTO_READ, false)
|
|
||||||
.remoteAddress(InetSocketAddress(InetAddress.getByName(relayInfo.host), relayInfo.port))
|
|
||||||
.connect()
|
|
||||||
.addListener { quicChannelFuture ->
|
|
||||||
if (!quicChannelFuture.isSuccess) {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
throw quicChannelFuture.cause()
|
|
||||||
}
|
|
||||||
quicChannel = quicChannelFuture.get() as QuicChannel
|
|
||||||
quicChannel!!.createStream(QuicStreamType.BIDIRECTIONAL,
|
|
||||||
object : ChannelInitializer<QuicStreamChannel>() {
|
|
||||||
override fun initChannel(ch: QuicStreamChannel) {
|
|
||||||
ch.pipeline().addLast(QuiclimeControlMessageCodec(), object : SimpleChannelInboundHandler<QuiclimeControlMessage>() {
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext?, msg: QuiclimeControlMessage?) {
|
|
||||||
when (msg) {
|
|
||||||
is DomainAssignmentCompleteMessageClientbound -> {
|
|
||||||
state = QuiclimeHandlerState.STARTED
|
|
||||||
ChatHelper.sendDomainAssignment(msg.domain)
|
|
||||||
}
|
|
||||||
is RequestMessageBroadcastMessageClientbound -> {
|
|
||||||
ChatHelper.sendLiteral(msg.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).addListener {
|
|
||||||
if (!it.isSuccess) {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
throw it.cause()
|
|
||||||
}
|
|
||||||
val streamChannel = it.now as QuicStreamChannel
|
|
||||||
LOGGER.info("control channel open: {}", streamChannel)
|
|
||||||
streamChannel
|
|
||||||
.writeAndFlush(RequestDomainAssignmentMessageServerbound())
|
|
||||||
.addListener {
|
|
||||||
LOGGER.info("control channel write complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
quicChannel!!.closeFuture().addListener {
|
|
||||||
datagramChannel?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
ChatHelper.sendError()
|
|
||||||
this.stop()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
state = QuiclimeHandlerState.STOPPING
|
|
||||||
if (quicChannel?.close()?.addListener {
|
|
||||||
if (datagramChannel?.close()?.addListener {
|
|
||||||
group.shutdownGracefully().addListener {
|
|
||||||
state = QuiclimeHandlerState.STOPPED
|
|
||||||
}
|
|
||||||
} == null) {
|
|
||||||
group.shutdownGracefully().addListener {
|
|
||||||
state = QuiclimeHandlerState.STOPPED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} == null) {
|
|
||||||
if (datagramChannel?.close()?.addListener {
|
|
||||||
group.shutdownGracefully().addListener {
|
|
||||||
state = QuiclimeHandlerState.STOPPED
|
|
||||||
}
|
|
||||||
} == null) {
|
|
||||||
group.shutdownGracefully().addListener {
|
|
||||||
state = QuiclimeHandlerState.STOPPED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,6 +20,6 @@ side="BOTH"
|
||||||
[[dependencies.${mod_id}]]
|
[[dependencies.${mod_id}]]
|
||||||
modId="minecraft"
|
modId="minecraft"
|
||||||
mandatory=true
|
mandatory=true
|
||||||
versionRange="${mc_version == "1.19.4" ? "[1.19,)" : "[1.18,1.19)"}"
|
versionRange="${mc_version == "1.19.4" ? "[1.19,)" : "[1.17,1.19)"}"
|
||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
side="BOTH"
|
side="BOTH"
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"text.e4mc_minecraft.domainAssigned": "Lokalt spel startat på domän [%s]",
|
|
||||||
"text.e4mc_minecraft.closeServer": "Lokalt spel är inte publikt öppet längre",
|
|
||||||
"text.e4mc_minecraft.serverAlreadyClosed": "Lokalt spel är inte öppet",
|
|
||||||
"text.e4mc_minecraft.clickToStop": " (Klicka här för att stoppa)",
|
|
||||||
"text.e4mc_minecraft.error": "Ett fel uppstod i e4mc"
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"text.e4mc_minecraft.domainAssigned": "将本地游戏托管在域名[%s]上",
|
|
||||||
"text.e4mc_minecraft.closeServer": "不再公开托管本地游戏",
|
|
||||||
"text.e4mc_minecraft.serverAlreadyClosed": "本地游戏没有被公开托管",
|
|
||||||
"text.e4mc_minecraft.clickToStop": "(点击这里以停止)"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"text.e4mc_minecraft.domainAssigned": "本地遊戲已託管在網域 [%s]",
|
|
||||||
"text.e4mc_minecraft.closeServer": "本地遊戲不再公開託管",
|
|
||||||
"text.e4mc_minecraft.serverAlreadyClosed": "本地遊戲未公開託管",
|
|
||||||
"text.e4mc_minecraft.clickToStop": "(點擊此處停止)",
|
|
||||||
"text.e4mc_minecraft.error": "在 e4mc 中發生了個錯誤"
|
|
||||||
}
|
|
|
@ -2,6 +2,5 @@
|
||||||
"text.e4mc_minecraft.domainAssigned": "Local game hosted on domain [%s]",
|
"text.e4mc_minecraft.domainAssigned": "Local game hosted on domain [%s]",
|
||||||
"text.e4mc_minecraft.closeServer": "Local game no longer publicly hosted",
|
"text.e4mc_minecraft.closeServer": "Local game no longer publicly hosted",
|
||||||
"text.e4mc_minecraft.serverAlreadyClosed": "Local game not publicly hosted",
|
"text.e4mc_minecraft.serverAlreadyClosed": "Local game not publicly hosted",
|
||||||
"text.e4mc_minecraft.clickToStop": " (Click here to stop)",
|
"text.e4mc_minecraft.clickToStop": " (Click here to stop)"
|
||||||
"text.e4mc_minecraft.error": "An error occurred in e4mc"
|
|
||||||
}
|
}
|
|
@ -2,6 +2,5 @@
|
||||||
"text.e4mc_minecraft.domainAssigned": "로컬 게임을 도메인 [%s]에서 호스트합니다",
|
"text.e4mc_minecraft.domainAssigned": "로컬 게임을 도메인 [%s]에서 호스트합니다",
|
||||||
"text.e4mc_minecraft.closeServer": "로컬 게임이 더이상 공개되지 않습니다",
|
"text.e4mc_minecraft.closeServer": "로컬 게임이 더이상 공개되지 않습니다",
|
||||||
"text.e4mc_minecraft.serverAlreadyClosed": "로컬 게임이 이미 공개중이 아닙니다",
|
"text.e4mc_minecraft.serverAlreadyClosed": "로컬 게임이 이미 공개중이 아닙니다",
|
||||||
"text.e4mc_minecraft.clickToStop": " (멉추려면 여기를 클릭하세요)",
|
"text.e4mc_minecraft.clickToStop": " (멉추려면 여기를 클릭하세요)"
|
||||||
"text.e4mc_minecraft.error": "e4mc에서 오류가 발생했습니다."
|
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"required": true,
|
"required": true,
|
||||||
"package": "vg.skye.e4mc_minecraft.mixins",
|
"package": "vg.skye.e4mc_minecraft.mixins",
|
||||||
"compatibilityLevel": "JAVA_16",
|
"compatibilityLevel": "JAVA_16",
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"ClientConnectionMixin",
|
"ServerNetworkIoMixin",
|
||||||
"ServerNetworkIoMixin"
|
"ClientConnectionMixin"
|
||||||
],
|
],
|
||||||
"client": [],
|
"client": [],
|
||||||
"server": []
|
"server": []
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
"sources": "https://git.skye.vg/me/e4mc_minecraft"
|
"sources": "https://git.skye.vg/me/e4mc_minecraft"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/e4mc_minecraft/icon.png",
|
|
||||||
"environment": "*",
|
"environment": "*",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
"fabric": "*",
|
"fabric": "*",
|
||||||
"fabricloader": ">=0.14.9",
|
"fabricloader": ">=0.14.9",
|
||||||
"fabric-language-kotlin": "*",
|
"fabric-language-kotlin": "*",
|
||||||
"minecraft": "${mc_version == "1.19.4" ? ">=1.19" : ">=1.18 <1.19"}",
|
"minecraft": "${mc_version == "1.19.4" ? ">=1.19" : ">=1.17 <1.19"}",
|
||||||
"java": ">=16"
|
"java": ">=16"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import com.modrinth.minotaur.dependencies.DependencyType
|
import com.modrinth.minotaur.dependencies.DependencyType
|
||||||
import com.modrinth.minotaur.dependencies.ModDependency
|
import com.modrinth.minotaur.dependencies.ModDependency
|
||||||
import org.gradle.configurationcache.extensions.capitalized
|
import org.gradle.configurationcache.extensions.capitalized
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import xyz.deftu.gradle.tools.minecraft.CurseRelation
|
import xyz.deftu.gradle.tools.minecraft.CurseRelation
|
||||||
import xyz.deftu.gradle.tools.minecraft.CurseRelationType
|
import xyz.deftu.gradle.tools.minecraft.CurseRelationType
|
||||||
|
|
||||||
|
@ -15,33 +16,27 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
val bundle by configurations.creating {
|
val bundle by configurations.creating {
|
||||||
// Fabric imposes a hard limit of 64 on mod IDs
|
if (mcData.isFabric) {
|
||||||
// the autogenned mod IDs are far longer than that
|
|
||||||
// thanks, netty!
|
|
||||||
if (false /* mcData.isFabric */) {
|
|
||||||
configurations.getByName("include").extendsFrom(this)
|
configurations.getByName("include").extendsFrom(this)
|
||||||
} else configurations.getByName("shade").extendsFrom(this)
|
} else configurations.getByName("shade").extendsFrom(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolkitLoomHelper {
|
loomHelper {
|
||||||
if (mcData.isForge) {
|
if (mcData.isForge) {
|
||||||
useForgeMixin("e4mc_minecraft.mixins.json", true)
|
useForgeMixin("e4mc_minecraft.mixins.json", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
releases {
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
toolkitReleases {
|
|
||||||
gameVersions.set(when (mcData.version) {
|
gameVersions.set(when (mcData.version) {
|
||||||
11904 -> listOf("1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1")
|
11904 -> listOf("1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4")
|
||||||
11802 -> listOf("1.18", "1.18.1", "1.18.2")
|
11802 -> listOf("1.17", "1.17.1", "1.18", "1.18.1", "1.18.2")
|
||||||
else -> listOf()
|
else -> listOf()
|
||||||
})
|
})
|
||||||
|
version.set("${modData.version}+${mcData.versionStr}-${mcData.loader.name}")
|
||||||
releaseName.set("[${when (mcData.version) {
|
releaseName.set("[${when (mcData.version) {
|
||||||
11904 -> "1.19-"
|
11904 -> "1.19-"
|
||||||
11802 -> "1.18.x"
|
11802 -> "1.17-1.18.2"
|
||||||
else -> mcData.versionStr
|
else -> mcData.versionStr
|
||||||
}}] [${mcData.loader.name.capitalized()}] ${modData.version}")
|
}}] [${mcData.loader.name.capitalized()}] ${modData.version}")
|
||||||
if (mcData.isFabric) {
|
if (mcData.isFabric) {
|
||||||
|
@ -49,7 +44,6 @@ toolkitReleases {
|
||||||
}
|
}
|
||||||
modrinth {
|
modrinth {
|
||||||
projectId.set("qANg5Jrr")
|
projectId.set("qANg5Jrr")
|
||||||
useSourcesJar.set(true)
|
|
||||||
if (mcData.isFabric) {
|
if (mcData.isFabric) {
|
||||||
dependencies.set(
|
dependencies.set(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -108,24 +102,7 @@ dependencies {
|
||||||
} else if (mcData.isForge) {
|
} else if (mcData.isForge) {
|
||||||
implementation("thedarkcolour:kotlinforforge:3.8.0")
|
implementation("thedarkcolour:kotlinforforge:3.8.0")
|
||||||
}
|
}
|
||||||
|
bundle(implementation("org.java-websocket:Java-WebSocket:1.5.3") {
|
||||||
bundle(implementation("com.github.vgskye.netty-incubator-codec-quic:netty-incubator-codec-classes-quic:57a52c4") {
|
exclude(group = "org.slf4j")
|
||||||
exclude(group = "io.netty")
|
|
||||||
})
|
})
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-classes-quic:0.0.47.Final")!!)
|
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:linux-x86_64") {
|
|
||||||
// exclude(group = "io.netty")
|
|
||||||
// })
|
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:windows-x86_64") {
|
|
||||||
// exclude(group = "io.netty")
|
|
||||||
// })
|
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:osx-x86_64") {
|
|
||||||
// exclude(group = "io.netty")
|
|
||||||
// })
|
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:linux-aarch_64") {
|
|
||||||
// exclude(group = "io.netty")
|
|
||||||
// })
|
|
||||||
// bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:osx-aarch_64") {
|
|
||||||
// exclude(group = "io.netty")
|
|
||||||
// })
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue