Harden sign keybind handling
Some checks failed
Build and Release / build (push) Failing after 3m43s
Some checks failed
Build and Release / build (push) Failing after 3m43s
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
package com.example.signleakshield;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class SignLeakShieldClient implements ClientModInitializer {
|
||||
public static final String MOD_ID = "signleakshield";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
SignLeakShieldTraceLog.reset();
|
||||
SignLeakShieldTraceLog.info("Sign Leak Shield initialized");
|
||||
}
|
||||
}
|
||||
package com.example.signleakshield;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class SignLeakShieldClient implements ClientModInitializer {
|
||||
public static final String MOD_ID = "signleakshield";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
SignLeakShieldTraceLog.reset();
|
||||
SignLeakShieldTraceLog.info("Sign Leak Shield initialized");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
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 synchronized void reset() {
|
||||
try {
|
||||
Files.deleteIfExists(LOG_PATH);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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 synchronized void reset() {
|
||||
try {
|
||||
Files.deleteIfExists(LOG_PATH);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,86 @@
|
||||
package com.example.signleakshield;
|
||||
|
||||
import net.minecraft.text.KeybindTextContent;
|
||||
import net.minecraft.text.PlainTextContent;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TextContent;
|
||||
import net.minecraft.text.TranslatableTextContent;
|
||||
|
||||
package com.example.signleakshield;
|
||||
|
||||
import net.minecraft.text.KeybindTextContent;
|
||||
import net.minecraft.text.PlainTextContent;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TextContent;
|
||||
import net.minecraft.text.TranslatableTextContent;
|
||||
|
||||
public final class TextSanitizer {
|
||||
private static final String[] BLACKLISTED_KEY_PARTS = {
|
||||
private static final String[] SUSPICIOUS_KEY_PARTS = {
|
||||
"gey.glazed.",
|
||||
"key.meteor-client.",
|
||||
"meteor-client",
|
||||
"meteorline",
|
||||
"meteorclient",
|
||||
"metiorclient",
|
||||
"meteordevelopment.meteorclient",
|
||||
"itemscroller",
|
||||
"moremousetweaks",
|
||||
"invmove",
|
||||
"autototem",
|
||||
"smartoffhand",
|
||||
"freecam",
|
||||
"jsmacros",
|
||||
"inventoryprofiles",
|
||||
"tweakeroo",
|
||||
"soundboard",
|
||||
"accurateblockplacement"
|
||||
};
|
||||
|
||||
private TextSanitizer() {
|
||||
}
|
||||
|
||||
"autototem",
|
||||
"smartoffhand",
|
||||
"freecam",
|
||||
"jsmacros",
|
||||
"inventoryprofiles",
|
||||
"tweakeroo",
|
||||
"soundboard",
|
||||
"accurateblockplacement"
|
||||
};
|
||||
|
||||
private TextSanitizer() {
|
||||
}
|
||||
|
||||
public static boolean isSuspicious(Text text) {
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
TextContent content = text.getContent();
|
||||
|
||||
if (content instanceof TranslatableTextContent translatable) {
|
||||
return isBlacklistedKey(translatable.getKey());
|
||||
if (isBlacklistedKey(translatable.getKey())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Object arg : translatable.getArgs()) {
|
||||
if (isSuspiciousArgument(arg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (content instanceof KeybindTextContent keybind) {
|
||||
return isBlacklistedKey(keybind.getKey());
|
||||
String key = keybind.getKey();
|
||||
return key != null && !VanillaKeybinds.isVanillaKey(key);
|
||||
}
|
||||
|
||||
for (Text sibling : text.getSiblings()) {
|
||||
if (isSuspicious(sibling)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String sanitize(Text text) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
append(text, out);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static void append(Text text, StringBuilder out) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextContent content = text.getContent();
|
||||
|
||||
if (content instanceof PlainTextContent plain) {
|
||||
out.append(plain.string());
|
||||
|
||||
for (Text sibling : text.getSiblings()) {
|
||||
if (isSuspicious(sibling)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String sanitize(Text text) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
append(text, out);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static void append(Text text, StringBuilder out) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextContent content = text.getContent();
|
||||
|
||||
if (content instanceof PlainTextContent plain) {
|
||||
out.append(plain.string());
|
||||
} else if (content instanceof TranslatableTextContent translatable) {
|
||||
String fallback = translatable.getFallback();
|
||||
if (fallback != null && !fallback.isEmpty()) {
|
||||
@@ -75,23 +89,24 @@ public final class TextSanitizer {
|
||||
out.append(translatable.getKey());
|
||||
}
|
||||
} else if (content instanceof KeybindTextContent keybind) {
|
||||
out.append(text.getString());
|
||||
String key = keybind.getKey();
|
||||
out.append(key != null ? key : "");
|
||||
} else {
|
||||
out.append(text.getString());
|
||||
}
|
||||
|
||||
for (Text sibling : text.getSiblings()) {
|
||||
append(sibling, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (Text sibling : text.getSiblings()) {
|
||||
append(sibling, out);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBlacklistedKey(String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lower = key.toLowerCase();
|
||||
for (String part : BLACKLISTED_KEY_PARTS) {
|
||||
for (String part : SUSPICIOUS_KEY_PARTS) {
|
||||
if (lower.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
@@ -99,4 +114,36 @@ public final class TextSanitizer {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSuspiciousArgument(Object arg) {
|
||||
if (arg == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arg instanceof Text text) {
|
||||
return isSuspicious(text);
|
||||
}
|
||||
|
||||
if (arg instanceof KeybindTextContent keybind) {
|
||||
return !VanillaKeybinds.isVanillaKey(keybind.getKey());
|
||||
}
|
||||
|
||||
if (arg instanceof TranslatableTextContent translatable) {
|
||||
if (isBlacklistedKey(translatable.getKey())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Object nested : translatable.getArgs()) {
|
||||
if (isSuspiciousArgument(nested)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arg instanceof CharSequence sequence) {
|
||||
return isBlacklistedKey(sequence.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.example.signleakshield;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class VanillaKeybinds {
|
||||
private static volatile Set<String> vanillaKeys;
|
||||
|
||||
private VanillaKeybinds() {
|
||||
}
|
||||
|
||||
public static boolean isVanillaKey(String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> keys = vanillaKeys;
|
||||
if (keys == null) {
|
||||
keys = loadVanillaKeys();
|
||||
if (keys != null) {
|
||||
vanillaKeys = keys;
|
||||
}
|
||||
}
|
||||
|
||||
return keys != null && keys.contains(key);
|
||||
}
|
||||
|
||||
private static Set<String> loadVanillaKeys() {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client == null || client.options == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> keys = new HashSet<>();
|
||||
|
||||
for (Field field : GameOptions.class.getDeclaredFields()) {
|
||||
Class<?> type = field.getType();
|
||||
if (!KeyBinding.class.isAssignableFrom(type) && !KeyBinding[].class.isAssignableFrom(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(client.options);
|
||||
collectKeyBindings(keys, value);
|
||||
} catch (IllegalAccessException | RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(keys);
|
||||
}
|
||||
|
||||
private static void collectKeyBindings(Set<String> keys, Object value) {
|
||||
if (value instanceof KeyBinding keyBinding) {
|
||||
String translationKey = keyBinding.getTranslationKey();
|
||||
if (translationKey != null && !translationKey.isEmpty()) {
|
||||
keys.add(translationKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof KeyBinding[] keyBindings) {
|
||||
for (KeyBinding keyBinding : keyBindings) {
|
||||
if (keyBinding == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String translationKey = keyBinding.getTranslationKey();
|
||||
if (translationKey != null && !translationKey.isEmpty()) {
|
||||
keys.add(translationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,109 @@
|
||||
package com.example.signleakshield.mixin;
|
||||
|
||||
import com.example.signleakshield.ExploitState;
|
||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||
import com.example.signleakshield.TextSanitizer;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(ClientConnection.class)
|
||||
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 allowed: captured sign missing or not blacklisted pos=%s front=%s capturedPresent=%s",
|
||||
pos,
|
||||
signPacket.isFront(),
|
||||
captured != null
|
||||
);
|
||||
return packet;
|
||||
}
|
||||
|
||||
Text[] textLines;
|
||||
textLines = signPacket.isFront() ? captured.getFront() : captured.getBack();
|
||||
|
||||
String line1 = TextSanitizer.sanitize(textLines[0]);
|
||||
String line2 = TextSanitizer.sanitize(textLines[1]);
|
||||
String line3 = TextSanitizer.sanitize(textLines[2]);
|
||||
String line4 = TextSanitizer.sanitize(textLines[3]);
|
||||
|
||||
SignLeakShieldTraceLog.info(
|
||||
"Blocked forced sign translation event at %s front=%s: got=%s, returned=%s",
|
||||
pos,
|
||||
signPacket.isFront(),
|
||||
Arrays.toString(signPacket.getText()),
|
||||
Arrays.toString(new String[] { line1, line2, line3, line4 })
|
||||
);
|
||||
|
||||
ExploitState.clearForcedOpen();
|
||||
return new UpdateSignC2SPacket(pos, signPacket.isFront(), line1, line2, line3, line4);
|
||||
}
|
||||
}
|
||||
package com.example.signleakshield.mixin;
|
||||
|
||||
import com.example.signleakshield.ExploitState;
|
||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||
import com.example.signleakshield.TextSanitizer;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(ClientConnection.class)
|
||||
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 allowed: captured sign missing or not blacklisted pos=%s front=%s capturedPresent=%s",
|
||||
pos,
|
||||
signPacket.isFront(),
|
||||
captured != null
|
||||
);
|
||||
return packet;
|
||||
}
|
||||
|
||||
Text[] textLines;
|
||||
textLines = signPacket.isFront() ? captured.getFront() : captured.getBack();
|
||||
|
||||
String line1 = TextSanitizer.sanitize(textLines[0]);
|
||||
String line2 = TextSanitizer.sanitize(textLines[1]);
|
||||
String line3 = TextSanitizer.sanitize(textLines[2]);
|
||||
String line4 = TextSanitizer.sanitize(textLines[3]);
|
||||
|
||||
SignLeakShieldTraceLog.info(
|
||||
"Blocked forced sign translation event at %s front=%s: got=%s, returned=%s",
|
||||
pos,
|
||||
signPacket.isFront(),
|
||||
Arrays.toString(signPacket.getText()),
|
||||
Arrays.toString(new String[] { line1, line2, line3, line4 })
|
||||
);
|
||||
|
||||
ExploitState.clearForcedOpen();
|
||||
return new UpdateSignC2SPacket(pos, signPacket.isFront(), line1, line2, line3, line4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
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;
|
||||
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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) {
|
||||
if (packet.getNbt() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getBlockEntityType() != BlockEntityType.SIGN && packet.getBlockEntityType() != BlockEntityType.HANGING_SIGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos pos = packet.getPos();
|
||||
ExploitState.SIGNS.put(pos.toImmutable(), SignTextExtractor.fromNbt(packet.getNbt()));
|
||||
}
|
||||
}
|
||||
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;
|
||||
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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) {
|
||||
if (packet.getNbt() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getBlockEntityType() != BlockEntityType.SIGN && packet.getBlockEntityType() != BlockEntityType.HANGING_SIGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos pos = packet.getPos();
|
||||
ExploitState.SIGNS.put(pos.toImmutable(), SignTextExtractor.fromNbt(packet.getNbt()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
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;
|
||||
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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) {
|
||||
packet.getChunkData().getBlockEntities(packet.getChunkX(), packet.getChunkZ()).accept((localPos, type, nbt) -> {
|
||||
if (nbt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != BlockEntityType.SIGN && type != BlockEntityType.HANGING_SIGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos pos = localPos.toImmutable();
|
||||
ExploitState.SIGNS.put(pos, SignTextExtractor.fromNbt((NbtCompound) nbt));
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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) {
|
||||
packet.getChunkData().getBlockEntities(packet.getChunkX(), packet.getChunkZ()).accept((localPos, type, nbt) -> {
|
||||
if (nbt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != BlockEntityType.SIGN && type != BlockEntityType.HANGING_SIGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos pos = localPos.toImmutable();
|
||||
ExploitState.SIGNS.put(pos, SignTextExtractor.fromNbt((NbtCompound) nbt));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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());
|
||||
}
|
||||
}
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user