24 Commits

Author SHA1 Message Date
96792a0091 CI
All checks were successful
Build and Release / build (push) Successful in 4m5s
2026-04-06 17:49:20 -05:00
08bd2aa272 working on CI
Some checks failed
Main Release / build (push) Failing after 16s
2026-04-06 17:47:04 -05:00
4e70c0e57e Merge branch 'dev'
Some checks failed
Main Release / build (push) Failing after 21s
2026-04-06 17:42:18 -05:00
988c2338fd Make workflows work on GitHub and Gitea
Some checks failed
Dev Verify / verify (push) Failing after 26s
2026-04-06 17:38:21 -05:00
43630597dd bump version 2026-04-06 17:36:21 -05:00
b03444d986 Reset trace log on client init 2026-04-06 17:36:07 -05:00
fe4675d606 Narrow sign exploit blacklist 2026-04-06 17:32:56 -05:00
c94c29cb5f Delete mixin trace logging code 2026-04-06 17:28:57 -05:00
e82f89791f Blacklist mod translation keys 2026-04-06 17:25:32 -05:00
91454b90a9 Allow vanilla translation text 2026-04-06 17:23:01 -05:00
9f597b4672 Preserve fallback sign text in rewrite 2026-04-06 17:19:17 -05:00
81d98ac840 Add file tracing for sign hooks 2026-04-06 17:11:17 -05:00
dbee665fd5 Merge branch 'dev'
Some checks failed
Main Release / build (push) Failing after 3m21s
2026-04-06 16:58:21 -05:00
437d9b0496 Undid all of the stupid optimizations in CI
Some checks failed
Dev Verify / verify (push) Failing after 3m27s
2026-04-06 16:55:58 -05:00
2fce517e18 optimized dev workflow
Some checks failed
Dev Verify / verify (push) Has been cancelled
2026-04-06 16:50:39 -05:00
0d0b4900b0 optimze some CI
Some checks failed
Main Release / build (push) Failing after 4m32s
2026-04-06 16:49:52 -05:00
1713748c11 Fix client refmap loading
Some checks failed
Main Release / build (push) Has been cancelled
2026-04-06 16:46:33 -05:00
7db4423fe2 Split release and verify workflows 2026-04-06 16:43:56 -05:00
096df79aca updated gitignore 2026-04-06 16:40:16 -05:00
562748b79d Fix mixin remapping for client hooks
All checks were successful
Build and Release / build (push) Successful in 3m48s
2026-04-06 16:39:33 -05:00
be700eb008 Fix outgoing packet mixin target
All checks were successful
Build and Release / build (push) Successful in 3m49s
2026-04-06 16:23:52 -05:00
b004e05575 Fix client network mixin targets
All checks were successful
Build and Release / build (push) Successful in 3m37s
2026-04-06 16:11:26 -05:00
5862f0bb1e Fix logging mixin and speed up builds
Some checks failed
Build and Release / build (push) Has been cancelled
2026-04-06 16:10:39 -05:00
2657bbe5af Fix chunk data mixin target
All checks were successful
Build and Release / build (push) Successful in 3m35s
2026-04-06 16:06:52 -05:00
14 changed files with 213 additions and 21 deletions

View File

@@ -0,0 +1,29 @@
name: Dev Verify
on:
push:
branches:
- dev
pull_request:
branches:
- dev
workflow_dispatch:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Verify
shell: bash
run: |
set -euo pipefail
bash ./gradlew --no-daemon compileClientJava

View File

@@ -39,7 +39,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
gradle --no-daemon clean build
gradle --no-daemon build
- name: Publish Gitea release
if: github.event_name != 'pull_request'

5
.gitignore vendored
View File

@@ -24,3 +24,8 @@
hs_err_pid*
replay_pid*
.gradle/*
.gradle/
build/*
build/

View File

@@ -4,6 +4,9 @@
>
> This mod was built with the help of AI.
![Main Release](https://dock-it.dev/OnniSystems/ModBlockBypass/actions/workflows/main-release.yml/badge.svg?branch=main)
![Dev Verify](https://dock-it.dev/OnniSystems/ModBlockBypass/actions/workflows/dev-verify.yml/badge.svg?branch=dev)
This is a Fabric client mod for Minecraft 1.21.1 that defensively patches forced sign-editor translation and keybind leak probes.
Bypass's Anti Mod systems on Donut SMP and otehr like it.

View File

@@ -5,6 +5,7 @@ plugins {
version = project.mod_version
group = project.maven_group
def modVersion = project.version.toString()
base {
archivesName = project.archives_base_name
@@ -15,6 +16,11 @@ repositories {
}
loom {
mixin {
useLegacyMixinAp = true
defaultRefmapName = "client-signleakshield.refmap.json"
}
splitEnvironmentSourceSets()
mods {
@@ -33,10 +39,10 @@ dependencies {
}
processResources {
inputs.property "version", project.version
inputs.property "version", modVersion
filesMatching("fabric.mod.json") {
expand "version": inputs.properties.version
expand "version": modVersion
}
}

View File

@@ -1,6 +1,8 @@
org.gradle.jvmargs=-Xmx2G
org.gradle.jvmargs=-Xmx2G -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.configuration-cache=true
minecraft_version=1.21.1
yarn_mappings=1.21.1+build.3
@@ -8,6 +10,6 @@ loader_version=0.18.4
loom_version=1.9.1
fabric_version=0.116.8+1.21.1
mod_version=1.0.0
mod_version=1.0.1
maven_group=com.example
archives_base_name=sign-leak-shield
archives_base_name=sign-leak-shield

View File

@@ -10,6 +10,7 @@ public final class SignLeakShieldClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
LOGGER.info("Sign Leak Shield initialized");
SignLeakShieldTraceLog.reset();
SignLeakShieldTraceLog.info("Sign Leak Shield initialized");
}
}

View File

@@ -0,0 +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) {
}
}
}

View File

@@ -7,6 +7,24 @@ import net.minecraft.text.TextContent;
import net.minecraft.text.TranslatableTextContent;
public final class TextSanitizer {
private static final String[] BLACKLISTED_KEY_PARTS = {
"gey.glazed.",
"meteorline",
"meteorclient",
"metiorclient",
"itemscroller",
"moremousetweaks",
"invmove",
"autototem",
"smartoffhand",
"freecam",
"jsmacros",
"inventoryprofiles",
"tweakeroo",
"soundboard",
"accurateblockplacement"
};
private TextSanitizer() {
}
@@ -17,8 +35,12 @@ public final class TextSanitizer {
TextContent content = text.getContent();
if (content instanceof TranslatableTextContent || content instanceof KeybindTextContent) {
return true;
if (content instanceof TranslatableTextContent translatable) {
return isBlacklistedKey(translatable.getKey());
}
if (content instanceof KeybindTextContent keybind) {
return isBlacklistedKey(keybind.getKey());
}
for (Text sibling : text.getSiblings()) {
@@ -53,7 +75,7 @@ public final class TextSanitizer {
out.append(translatable.getKey());
}
} else if (content instanceof KeybindTextContent keybind) {
out.append(keybind.getKey());
out.append(text.getString());
} else {
out.append(text.getString());
}
@@ -62,4 +84,19 @@ public final class TextSanitizer {
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) {
if (lower.contains(part)) {
return true;
}
}
return false;
}
}

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;
@@ -17,44 +17,86 @@ import java.util.Arrays;
@Mixin(ClientConnection.class)
public abstract class ClientConnectionMixin {
@ModifyVariable(method = "send(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;Z)V", at = @At("HEAD"), argsOnly = true)
@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[] lines = signPacket.isFront() ? captured.getFront() : captured.getBack();
String line1 = TextSanitizer.sanitize(lines[0]);
String line2 = TextSanitizer.sanitize(lines[1]);
String line3 = TextSanitizer.sanitize(lines[2]);
String line4 = TextSanitizer.sanitize(lines[3]);
Text[] textLines;
textLines = signPacket.isFront() ? captured.getFront() : captured.getBack();
SignLeakShieldClient.LOGGER.info(
"Blocked forced sign translation event at {} front={}: got={}, returned={}",
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()),

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;
@@ -13,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerBlockEntityUpdateMixin {
@Inject(method = "onBlockEntityUpdate", at = @At("HEAD"))
@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;

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;
@@ -14,7 +15,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerChunkDataMixin {
@Inject(method = "onChunkData", at = @At("HEAD"))
@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) {

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());
}
}

View File

@@ -1,6 +1,7 @@
{
"required": true,
"package": "com.example.signleakshield.mixin",
"refmap": "client-signleakshield.refmap.json",
"compatibilityLevel": "JAVA_21",
"client": [
"ClientConnectionMixin",