Add file tracing for sign hooks

This commit is contained in:
2026-04-06 17:11:17 -05:00
parent 437d9b0496
commit 81d98ac840
5 changed files with 125 additions and 3 deletions

View File

@@ -0,0 +1,51 @@
package com.example.signleakshield;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public final class SignLeakShieldTraceLog {
private static final Path LOG_PATH = Paths.get("logs", "signleakshield-trace.log");
private SignLeakShieldTraceLog() {
}
public static void info(String message) {
SignLeakShieldClient.LOGGER.info(message);
append(message);
}
public static void info(String format, Object... args) {
info(String.format(format, args));
}
private static synchronized void append(String message) {
try {
Path parent = LOG_PATH.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
String line = String.format(
"%s %s%n",
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now()),
message
);
Files.writeString(
LOG_PATH,
line,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND,
StandardOpenOption.WRITE
);
} catch (IOException ignored) {
}
}
}

View File

@@ -1,7 +1,7 @@
package com.example.signleakshield.mixin;
import com.example.signleakshield.ExploitState;
import com.example.signleakshield.SignLeakShieldClient;
import com.example.signleakshield.SignLeakShieldTraceLog;
import com.example.signleakshield.TextSanitizer;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketCallbacks;
@@ -19,31 +19,71 @@ import java.util.Arrays;
public abstract class ClientConnectionMixin {
@ModifyVariable(method = "send(Lnet/minecraft/network/packet/Packet;)V", at = @At("HEAD"), argsOnly = true)
private Packet<?> signleakshield$rewriteOutgoing(Packet<?> packet) {
if (packet instanceof UpdateSignC2SPacket signPacket) {
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(signPacket.getPos());
SignLeakShieldTraceLog.info(
"Outgoing sign packet observed: pos=%s front=%s forcedOpen=%s captured=%s packetText=%s",
signPacket.getPos(),
signPacket.isFront(),
forcedOpen == null ? "null" : forcedOpen.pos() + "/front=" + forcedOpen.front() + "/ageMs=" + (System.currentTimeMillis() - forcedOpen.timeMs()),
captured == null ? "null" : "suspicious=" + captured.isSuspicious(),
Arrays.toString(signPacket.getText())
);
}
if (!(packet instanceof UpdateSignC2SPacket signPacket)) {
return packet;
}
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
if (forcedOpen == null) {
SignLeakShieldTraceLog.info(
"Outgoing sign packet not rewritten: no pending forced-open context for pos=%s front=%s",
signPacket.getPos(),
signPacket.isFront()
);
return packet;
}
BlockPos pos = signPacket.getPos();
if (!forcedOpen.pos().equals(pos)) {
SignLeakShieldTraceLog.info(
"Outgoing sign packet not rewritten: pos mismatch packet=%s forcedOpen=%s",
pos,
forcedOpen.pos()
);
return packet;
}
if (forcedOpen.front() != signPacket.isFront()) {
SignLeakShieldTraceLog.info(
"Outgoing sign packet not rewritten: front mismatch packet=%s forcedOpen=%s",
signPacket.isFront(),
forcedOpen.front()
);
return packet;
}
long ageMs = System.currentTimeMillis() - forcedOpen.timeMs();
if (ageMs < 0L || ageMs > 5000L) {
SignLeakShieldTraceLog.info(
"Outgoing sign packet not rewritten: forced-open age out of range pos=%s front=%s ageMs=%s",
pos,
signPacket.isFront(),
ageMs
);
return packet;
}
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(pos);
if (captured == null || !captured.isSuspicious()) {
SignLeakShieldTraceLog.info(
"Outgoing sign packet not rewritten: captured sign missing or not suspicious pos=%s front=%s capturedPresent=%s",
pos,
signPacket.isFront(),
captured != null
);
return packet;
}
@@ -53,8 +93,8 @@ public abstract class ClientConnectionMixin {
String line3 = TextSanitizer.sanitize(lines[2]);
String line4 = TextSanitizer.sanitize(lines[3]);
SignLeakShieldClient.LOGGER.info(
"Blocked forced sign translation event at {} front={}: got={}, returned={}",
SignLeakShieldTraceLog.info(
"Blocked forced sign translation event at %s front=%s: got=%s, returned=%s",
pos,
signPacket.isFront(),
Arrays.toString(signPacket.getText()),

View File

@@ -1,6 +1,7 @@
package com.example.signleakshield.mixin;
import com.example.signleakshield.ExploitState;
import com.example.signleakshield.SignLeakShieldTraceLog;
import com.example.signleakshield.SignTextExtractor;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.client.network.ClientPlayNetworkHandler;
@@ -15,6 +16,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class ClientPlayNetworkHandlerBlockEntityUpdateMixin {
@Inject(method = "onBlockEntityUpdate(Lnet/minecraft/network/packet/s2c/play/BlockEntityUpdateS2CPacket;)V", at = @At("HEAD"))
private void signleakshield$captureSign(BlockEntityUpdateS2CPacket packet, CallbackInfo ci) {
SignLeakShieldTraceLog.info(
"Block entity update received: pos=%s type=%s hasNbt=%s",
packet.getPos(),
packet.getBlockEntityType(),
packet.getNbt() != null
);
if (packet.getNbt() == null) {
return;
}
@@ -24,6 +31,12 @@ public abstract class ClientPlayNetworkHandlerBlockEntityUpdateMixin {
}
BlockPos pos = packet.getPos();
SignLeakShieldTraceLog.info(
"Block entity sign captured: pos=%s type=%s nbtKeys=%s",
pos,
packet.getBlockEntityType(),
packet.getNbt().getKeys()
);
ExploitState.SIGNS.put(pos.toImmutable(), SignTextExtractor.fromNbt(packet.getNbt()));
}
}

View File

@@ -1,6 +1,7 @@
package com.example.signleakshield.mixin;
import com.example.signleakshield.ExploitState;
import com.example.signleakshield.SignLeakShieldTraceLog;
import com.example.signleakshield.SignTextExtractor;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.client.network.ClientPlayNetworkHandler;
@@ -16,6 +17,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class ClientPlayNetworkHandlerChunkDataMixin {
@Inject(method = "onChunkData(Lnet/minecraft/network/packet/s2c/play/ChunkDataS2CPacket;)V", at = @At("HEAD"))
private void signleakshield$captureChunkData(ChunkDataS2CPacket packet, CallbackInfo ci) {
SignLeakShieldTraceLog.info(
"Chunk data received: chunkX=%s chunkZ=%s",
packet.getChunkX(),
packet.getChunkZ()
);
packet.getChunkData().getBlockEntities(packet.getChunkX(), packet.getChunkZ()).accept((localPos, type, nbt) -> {
if (nbt == null) {
return;
@@ -26,6 +32,12 @@ public abstract class ClientPlayNetworkHandlerChunkDataMixin {
}
BlockPos pos = localPos.toImmutable();
SignLeakShieldTraceLog.info(
"Chunk sign captured: pos=%s type=%s nbtKeys=%s",
pos,
type,
nbt.getKeys()
);
ExploitState.SIGNS.put(pos, SignTextExtractor.fromNbt((NbtCompound) nbt));
});
}

View File

@@ -1,6 +1,7 @@
package com.example.signleakshield.mixin;
import com.example.signleakshield.ExploitState;
import com.example.signleakshield.SignLeakShieldTraceLog;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.SignEditorOpenS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
@@ -12,6 +13,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class ClientPlayNetworkHandlerSignEditorOpenMixin {
@Inject(method = "onSignEditorOpen", at = @At("HEAD"))
private void signleakshield$rememberForcedOpen(SignEditorOpenS2CPacket packet, CallbackInfo ci) {
SignLeakShieldTraceLog.info(
"Sign editor open received: pos=%s front=%s",
packet.getPos(),
packet.isFront()
);
ExploitState.rememberForcedOpen(packet.getPos(), packet.isFront());
}
}