Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ public class MixinClientPacketListener {
/*import net.minecraft.client.network.handler.ClientPlayNetworkHandler;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import org.polyfrost.polyplus.client.emoji.EmojiRegistry;
import org.polyfrost.polyplus.client.network.p2p.P2PSessionManager;
import org.polyfrost.polyplus.client.serverapi.OneClientServerApi;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
Expand All @@ -49,6 +51,19 @@ public class MixinClientPacketListener {
@Inject(method = "closeWorld", at = @At("HEAD"))
private void polyplus$onDisconnect(CallbackInfo ci) {
P2PSessionManager.onClientDisconnected();
OneClientServerApi.reset();
}

@Inject(
method = "handleCustomPayload",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/PacketUtils;ensureOnSameThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/handler/PacketHandler;Lnet/minecraft/util/BlockableEventLoop;)V",
shift = At.Shift.AFTER
)
)
private void polyplus$oneClientServerApi(CustomPayloadS2CPacket packet, CallbackInfo ci) {
OneClientServerApi.onLegacyPayload(packet.getChannel(), packet.getData());
}
}
*///?}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import org.polyfrost.polyplus.client.privacy.PrivacyGate
import org.polyfrost.polyplus.client.privacy.RichTextPrivacy
import org.polyfrost.polyplus.client.social.FriendsRepository
import org.polyfrost.polyplus.client.social.GroupsRepository
import org.polyfrost.polyplus.client.serverapi.OneClientServerApi
import org.polyfrost.polyplus.client.social.SessionsRepository
import org.polyfrost.polyplus.client.utils.ClientPlatform
import org.polyfrost.polyplus.client.utils.runSuspendCatching
Expand Down Expand Up @@ -174,6 +175,7 @@ object PolyPlusClient {
step("login gate") { MinecraftLoginGate.register() }
step("session refresh") { SessionRefresh.register() }
step("featured servers") { FeaturedServers.warmUp() }
step("oneclient server api") { OneClientServerApi.initialize() }
//? if wwaypoints
step("wwaypoints compat") { WWaypointsCompat.initialize() }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.polyfrost.polyplus.client.serverapi

import net.fabricmc.loader.api.FabricLoader
import org.apache.logging.log4j.LogManager
import org.polyfrost.polyplus.PolyPlusConstants
import java.util.concurrent.atomic.AtomicBoolean

//? if > 1.8.9 {
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry
//?} else {
/*import io.netty.buffer.Unpooled
import net.minecraft.client.Minecraft
import net.minecraft.network.PacketByteBuf
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket
*///?}

//? if >= 26.1 {
import net.fabricmc.fabric.api.client.networking.v1.ServerboundPlayChannelEvents
//?} else if > 1.8.9 {
/*import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents
*///?}

object OneClientServerApi {
private val LOGGER = LogManager.getLogger("${PolyPlusConstants.NAME}/ServerApi")
private val helloSent = AtomicBoolean(false)

@Volatile
var serverApiVersion: String? = null
private set

val isConnected: Boolean
get() = serverApiVersion != null

fun initialize() {
//? if >= 26.1 {
PayloadTypeRegistry.serverboundPlay().register(ServerApiPayload.TYPE, ServerApiPayload.CODEC)
PayloadTypeRegistry.clientboundPlay().register(ServerApiPayload.TYPE, ServerApiPayload.CODEC)
ServerboundPlayChannelEvents.REGISTER.register { _, _, _, channels ->
if (ServerApiPayload.TYPE.id() in channels) sendHello()
}
//?} else if > 1.8.9 {
/*PayloadTypeRegistry.playC2S().register(ServerApiPayload.TYPE, ServerApiPayload.CODEC)
PayloadTypeRegistry.playS2C().register(ServerApiPayload.TYPE, ServerApiPayload.CODEC)
C2SPlayChannelEvents.REGISTER.register { _, _, _, channels ->
if (ServerApiPayload.TYPE.id() in channels) sendHello()
}
*///?}

//? if > 1.8.9 {
ClientPlayNetworking.registerGlobalReceiver(ServerApiPayload.TYPE) { payload, _ -> handle(payload.data) }
ClientPlayConnectionEvents.JOIN.register { _, _, _ ->
if (ClientPlayNetworking.canSend(ServerApiPayload.TYPE)) sendHello()
}
ClientPlayConnectionEvents.DISCONNECT.register { _, _ -> reset() }
//?}
}

//? if = 1.8.9 {
/*@JvmStatic
fun onLegacyPayload(channel: String, data: PacketByteBuf) {
when (channel) {
"REGISTER" -> {
val channels = data.toString(Charsets.UTF_8).split('\u0000')
if (ServerApiProtocol.CHANNEL in channels) sendHello()
}
ServerApiProtocol.CHANNEL -> {
val bytes = ByteArray(data.readableBytes())
data.getBytes(data.readerIndex(), bytes)
handle(bytes)
}
}
}
*///?}

@JvmStatic
fun reset() {
helloSent.set(false)
serverApiVersion = null
}

private fun sendHello() {
if (!helloSent.compareAndSet(false, true)) return

val minecraftVersion = FabricLoader.getInstance()
.getModContainer("minecraft")
.map { it.metadata.version.friendlyString }
.orElse("unknown")
val hello = ServerApiProtocol.encodeHello(PolyPlusConstants.VERSION, minecraftVersion)

//? if > 1.8.9 {
ClientPlayNetworking.send(ServerApiPayload(hello))
//?} else {
/*val connection = Minecraft.getInstance().networkHandler ?: return
connection.sendPacket(CustomPayloadC2SPacket("REGISTER", PacketByteBuf(Unpooled.wrappedBuffer(ServerApiProtocol.CHANNEL.toByteArray(Charsets.UTF_8)))))
connection.sendPacket(CustomPayloadC2SPacket(ServerApiProtocol.CHANNEL, PacketByteBuf(Unpooled.wrappedBuffer(hello))))
*///?}
}

private fun handle(data: ByteArray) {
val packet = runCatching { ServerApiProtocol.decode(data) }
.onFailure { LOGGER.warn("Ignoring malformed OneClient server API packet: {}", it.message) }
.getOrNull()

when (packet) {
is ServerApiProtocol.Welcome -> {
serverApiVersion = packet.serverApiVersion
LOGGER.info("Server supports the OneClient server API {} (protocol {})", packet.serverApiVersion, packet.protocolVersion)
}
null -> Unit
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.polyfrost.polyplus.client.serverapi

//? if > 1.8.9 {
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
import net.minecraft.resources.Identifier

class ServerApiPayload(val data: ByteArray) : CustomPacketPayload {
override fun type(): CustomPacketPayload.Type<ServerApiPayload> = TYPE

companion object {
@JvmField
val TYPE = CustomPacketPayload.Type<ServerApiPayload>(
Identifier.fromNamespaceAndPath(ServerApiProtocol.NAMESPACE, ServerApiProtocol.PATH)
)

@JvmField
val CODEC: StreamCodec<FriendlyByteBuf, ServerApiPayload> = StreamCodec.of(
{ buf, payload -> buf.writeBytes(payload.data) },
{ buf -> ServerApiPayload(ByteArray(buf.readableBytes()).also { buf.readBytes(it) }) },
)
}
}
//?}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.polyfrost.polyplus.client.serverapi

import java.io.ByteArrayOutputStream

object ServerApiProtocol {
const val NAMESPACE = "oneclient"
const val PATH = "api"
const val CHANNEL = "$NAMESPACE:$PATH"
const val VERSION = 1

private const val SERVERBOUND_HELLO = 0x00
private const val CLIENTBOUND_WELCOME = 0x00
private const val MAX_STRING_BYTES = 256

sealed interface Clientbound
data class Welcome(val protocolVersion: Int, val serverApiVersion: String) : Clientbound

fun encodeHello(clientVersion: String, minecraftVersion: String): ByteArray =
Writer()
.varInt(SERVERBOUND_HELLO)
.varInt(VERSION)
.string(clientVersion)
.string(minecraftVersion)
.bytes()

fun decode(data: ByteArray): Clientbound? {
val reader = Reader(data)
return when (reader.varInt()) {
CLIENTBOUND_WELCOME -> Welcome(reader.varInt(), reader.string())
else -> null
}
}

private class Writer {
private val out = ByteArrayOutputStream()

fun varInt(value: Int): Writer {
var remaining = value
while (remaining and 0x7F.inv() != 0) {
out.write((remaining and 0x7F) or 0x80)
remaining = remaining ushr 7
}
out.write(remaining)
return this
}

fun string(value: String): Writer {
val encoded = value.toByteArray(Charsets.UTF_8)
varInt(encoded.size)
out.write(encoded, 0, encoded.size)
return this
}

fun bytes(): ByteArray = out.toByteArray()
}

private class Reader(private val data: ByteArray) {
private var position = 0

fun varInt(): Int {
var value = 0
for (shift in 0 until 35 step 7) {
val b = byte()
value = value or ((b and 0x7F) shl shift)
if (b and 0x80 == 0) return value
}
throw IllegalArgumentException("VarInt is too long")
}

fun string(): String {
val length = varInt()
require(length in 0..MAX_STRING_BYTES && length <= data.size - position) { "Bad string length $length" }
return String(data, position, length, Charsets.UTF_8).also { position += length }
}

private fun byte(): Int {
require(position < data.size) { "Unexpected end of packet" }
return data[position++].toInt() and 0xFF
}
}
}
Loading