Skip to content

Commit

Permalink
Merge branch 'main' into 1.20.2
Browse files Browse the repository at this point in the history
  • Loading branch information
senseiwells committed Feb 21, 2024
2 parents cec08f0 + 19493d8 commit e06bbab
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ around the carpet bot will be recorded.

#### Chunks

> **IMPORTANT NOTE:** While the mod will record the chunks you specify, the Minecraft client will **not** render the outermost chunks. So to record an area of **visible** chunks, you must add one chunk to your border, e.g. recording a visible area from `-5, -5` to `5, 5` you must record between `-6, -6` and `6, 6`.
To record an area of chunks on your server you can run `/replay start chunks from <chunkFromX> <chunkFromZ> to <chunkToX> <chunkToZ> in <dimension?> named <name?>`, for example:
```
/replay start chunks from -5 -5 to 5 5 in minecraft:overworld named MyChunkRecording
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.senseiwells.replay.ducks;

import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;

import java.util.Collection;

public interface ServerReplay$PackTracker {
void replay$addPacks(Collection<ClientboundResourcePackPushPacket> packs);

Collection<ClientboundResourcePackPushPacket> replay$getPacks();
}
6 changes: 6 additions & 0 deletions src/main/java/me/senseiwells/replay/mixin/CommandsMixin.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package me.senseiwells.replay.mixin;

import com.mojang.brigadier.CommandDispatcher;
import me.senseiwells.replay.ServerReplay;
import me.senseiwells.replay.commands.PackCommand;
import me.senseiwells.replay.commands.ReplayCommand;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
Expand All @@ -26,5 +28,9 @@ private void onRegisterCommands(
CallbackInfo ci
) {
ReplayCommand.register(this.dispatcher);

if (ServerReplay.config.getDebug()) {
PackCommand.register(this.dispatcher);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package me.senseiwells.replay.mixin.rejoin;

import me.senseiwells.replay.ducks.ServerReplay$PackTracker;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Mixin(ServerCommonPacketListenerImpl.class)
public class ServerCommonPacketListenerImplMixin implements ServerReplay$PackTracker {
// We need to keep track of what packs a player has...
// We don't really care if the player accepts / declines them, we'll record them anyway.
@Unique private final Map<UUID, ClientboundResourcePackPushPacket> replay$packs = new ConcurrentHashMap<>();

@Inject(
method = "send(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketSendListener;)V",
at = @At("HEAD")
)
private void onSendPacket(
Packet<?> packet,
@Nullable PacketSendListener packetSendListener,
CallbackInfo ci
) {
if (packet instanceof ClientboundResourcePackPushPacket resources) {
this.replay$packs.put(resources.id(), resources);
return;
}
if (packet instanceof ClientboundResourcePackPopPacket resources) {
Optional<UUID> uuid = resources.id();
if (uuid.isPresent()) {
this.replay$packs.remove(uuid.get());
} else {
this.replay$packs.clear();
}
}
}

@Override
public void replay$addPacks(Collection<ClientboundResourcePackPushPacket> packs) {
for (ClientboundResourcePackPushPacket packet : packs) {
this.replay$packs.put(packet.id(), packet);
}
}

@Override
public Collection<ClientboundResourcePackPushPacket> replay$getPacks() {
return this.replay$packs.values();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package me.senseiwells.replay.mixin.rejoin;

import com.llamalad7.mixinextras.sugar.Local;
import me.senseiwells.replay.ducks.ServerReplay$PackTracker;
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;
import net.minecraft.network.protocol.configuration.ServerboundFinishConfigurationPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerConfigurationPacketListenerImpl;
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 java.util.Collection;

@Mixin(ServerConfigurationPacketListenerImpl.class)
public class ServerConfigurationPacketListenerImplMixin {
@Inject(
method = "handleConfigurationFinished",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/Connection;resumeInboundAfterProtocolChange()V"
)
)
private void afterPlayerSpawned(
ServerboundFinishConfigurationPacket serverboundFinishConfigurationPacket,
CallbackInfo ci,
@Local ServerPlayer serverPlayer
) {
// Merge the packs into the GamePacketListener
Collection<ClientboundResourcePackPushPacket> packs = ((ServerReplay$PackTracker) this).replay$getPacks();
((ServerReplay$PackTracker) serverPlayer.connection).replay$addPacks(packs);
}
}
73 changes: 73 additions & 0 deletions src/main/kotlin/me/senseiwells/replay/commands/PackCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package me.senseiwells.replay.commands

import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import me.senseiwells.replay.ducks.`ServerReplay$PackTracker`
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.SharedSuggestionProvider
import net.minecraft.commands.arguments.UuidArgument
import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture

object PackCommand {
@JvmStatic
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
dispatcher.register(
Commands.literal("resource-pack").then(
Commands.literal("push").then(
Commands.argument("url", StringArgumentType.string()).then(
Commands.argument("uuid", UuidArgument.uuid()).executes(this::pushPack)
).executes { this.pushPack(it) }
)
).then(
Commands.literal("pop").then(
Commands.argument("uuid", UuidArgument.uuid()).suggests(this::suggestPacks).executes(this::popPack)
)
)
)
}

private fun pushPack(context: CommandContext<CommandSourceStack>): Int {
val url = StringArgumentType.getString(context, "url")
val uuid = UUID.nameUUIDFromBytes(url.encodeToByteArray())
return this.pushPack(context, uuid)
}

private fun pushPack(
context: CommandContext<CommandSourceStack>,
uuid: UUID = UuidArgument.getUuid(context, "uuid")
): Int {
val url = StringArgumentType.getString(context, "url")
val packet = ClientboundResourcePackPushPacket(uuid, url, "", false, null)
for (player in context.source.server.playerList.players) {
player.connection.send(packet)
}
return Command.SINGLE_SUCCESS
}

private fun popPack(context: CommandContext<CommandSourceStack>): Int {
val uuid = UuidArgument.getUuid(context, "uuid")
val packet = ClientboundResourcePackPopPacket(Optional.of(uuid))
for (player in context.source.server.playerList.players) {
player.connection.send(packet)
}
return Command.SINGLE_SUCCESS
}

private fun suggestPacks(
context: CommandContext<CommandSourceStack>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
val player = context.source.player ?: return Suggestions.empty()
val packs = (player.connection as `ServerReplay$PackTracker`).`replay$getPacks`()
return SharedSuggestionProvider.suggest(packs.map { it.id.toString() }, builder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ abstract class ReplayRecorder(
path.parent.createDirectories()
val bytes = URL(packet.url).openStream().readAllBytes()
path.writeBytes(bytes)
this.writeResourcePack(bytes, packet.hash, requestId)
if (!this.writeResourcePack(bytes, packet.hash, requestId)) {
ServerReplay.logger.error("Resource pack hashes do not match! Pack '${packet.url}' will not be loaded...")
}
}.exceptionally {
ServerReplay.logger.error("Failed to download resource pack", it)
null
Expand All @@ -454,7 +456,7 @@ abstract class ReplayRecorder(
private fun writeResourcePack(bytes: ByteArray, expectedHash: String, id: Int): Boolean {
@Suppress("DEPRECATION")
val packHash = Hashing.sha1().hashBytes(bytes).toString()
if (expectedHash == packHash) {
if (expectedHash == "" || expectedHash == packHash) {
this.executor.execute {
try {
val index = this.replay.resourcePackIndex ?: HashMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ class RejoinConfigurationPacketListener(
// configuration checks.
// We must manually pong.
this.handlePong(ServerboundPongPacket(0))
return
}
}

fun runConfigurationTasks() {
// We do not have to wait for the client to respond
for (task in this.tasks) {
task.start(this::send)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package me.senseiwells.replay.rejoin

import me.senseiwells.replay.api.ReplaySenders
import me.senseiwells.replay.chunk.ChunkRecorder
import me.senseiwells.replay.ducks.`ServerReplay$PackTracker`
import me.senseiwells.replay.player.PlayerRecorder
import me.senseiwells.replay.player.PlayerRecorders
import me.senseiwells.replay.recorder.ReplayRecorder
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.game.*
Expand All @@ -25,7 +25,10 @@ class RejoinedReplayPlayer private constructor(
val connection = RejoinConnection()
val cookies = CommonListenerCookie(player.gameProfile, 0, player.clientInformation())

RejoinConfigurationPacketListener(rejoined, connection, cookies).startConfiguration()
val config = RejoinConfigurationPacketListener(rejoined, connection, cookies)
config.startConfiguration()
rejoined.sendResourcePacks()
config.runConfigurationTasks()
recorder.afterConfigure()

rejoined.load(player.saveWithoutId(CompoundTag()))
Expand All @@ -37,6 +40,16 @@ class RejoinedReplayPlayer private constructor(
this.id = this.original.id
}

private fun sendResourcePacks() {
val connection = this.original.connection
// Our connection may be null if we're using a fake player
if (connection is `ServerReplay$PackTracker`) {
for (packet in connection.`replay$getPacks`()) {
this.recorder.record(packet)
}
}
}

private fun place(
connection: RejoinConnection,
cookies: CommonListenerCookie
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/serverreplay.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"player.TrackedEntityMixin",
"rejoin.ChunkMapAccessor",
"rejoin.ConnectionAccessor",
"rejoin.ServerCommonPacketListenerImplMixin",
"rejoin.ServerConfigurationPacketListenerImplAccessor",
"rejoin.ServerConfigurationPacketListenerImplMixin",
"rejoin.ServerPlayerMixin",
"rejoin.TrackedEntityAccessor",
"studio.CustomViaPlatformMixin",
Expand Down

0 comments on commit e06bbab

Please sign in to comment.