Compare commits
16 Commits
build-915c
...
5a9572336a
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a9572336a | |||
| 45fc0db2e5 | |||
| a17a7d2d6b | |||
| b08a749282 | |||
| 7d7d07d040 | |||
| 344cc6882b | |||
| b919c8e792 | |||
| 9cd722d585 | |||
| 96448073e8 | |||
| b18700285b | |||
| edf1bbc1e4 | |||
| 54dfddec33 | |||
| 4c8f3c23a4 | |||
| 12bbaf157d | |||
| 8eb03d3e83 | |||
| 945af4f565 |
@@ -12,18 +12,26 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: gradle:8.14.5-jdk21
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ github.token }}
|
||||||
|
GITEA_API_URL: ${{ github.server_url }}/api/v1
|
||||||
|
GITEA_REPO: ${{ github.repository }}
|
||||||
|
GITEA_REPO_URL: ${{ github.server_url }}/${{ github.repository }}.git
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Prepare source
|
||||||
uses: actions/checkout@v4
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
- name: Set up Java
|
if [ ! -f build.gradle ]; then
|
||||||
uses: actions/setup-java@v4
|
git -c http.extraHeader="Authorization: token ${GITEA_TOKEN}" \
|
||||||
with:
|
clone --depth 1 "${GITEA_REPO_URL}" .
|
||||||
distribution: temurin
|
fi
|
||||||
java-version: 21
|
|
||||||
|
|
||||||
- name: Verify
|
- name: Verify
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
bash ./gradlew --no-daemon compileClientJava
|
gradle --no-daemon compileClientJava
|
||||||
|
|||||||
@@ -16,14 +16,48 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: gradle:8.11.1-jdk21
|
image: gradle:8.14.5-jdk21
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- minecraft_version: "1.21.1"
|
||||||
|
yarn_mappings: "1.21.1+build.3"
|
||||||
|
fabric_version: "0.116.10+1.21.1"
|
||||||
|
- minecraft_version: "1.21.2"
|
||||||
|
yarn_mappings: "1.21.2+build.1"
|
||||||
|
fabric_version: "0.106.1+1.21.2"
|
||||||
|
- minecraft_version: "1.21.3"
|
||||||
|
yarn_mappings: "1.21.3+build.2"
|
||||||
|
fabric_version: "0.114.1+1.21.3"
|
||||||
|
- minecraft_version: "1.21.4"
|
||||||
|
yarn_mappings: "1.21.4+build.8"
|
||||||
|
fabric_version: "0.119.4+1.21.4"
|
||||||
|
- minecraft_version: "1.21.5"
|
||||||
|
yarn_mappings: "1.21.5+build.1"
|
||||||
|
fabric_version: "0.128.2+1.21.5"
|
||||||
|
- minecraft_version: "1.21.6"
|
||||||
|
yarn_mappings: "1.21.6+build.1"
|
||||||
|
fabric_version: "0.128.2+1.21.6"
|
||||||
|
- minecraft_version: "1.21.7"
|
||||||
|
yarn_mappings: "1.21.7+build.8"
|
||||||
|
fabric_version: "0.129.0+1.21.7"
|
||||||
|
- minecraft_version: "1.21.8"
|
||||||
|
yarn_mappings: "1.21.8+build.1"
|
||||||
|
fabric_version: "0.136.1+1.21.8"
|
||||||
|
- minecraft_version: "1.21.9"
|
||||||
|
yarn_mappings: "1.21.9+build.1"
|
||||||
|
fabric_version: "0.134.1+1.21.9"
|
||||||
|
- minecraft_version: "1.21.10"
|
||||||
|
yarn_mappings: "1.21.10+build.3"
|
||||||
|
fabric_version: "0.138.4+1.21.10"
|
||||||
|
- minecraft_version: "1.21.11"
|
||||||
|
yarn_mappings: "1.21.11+build.4"
|
||||||
|
fabric_version: "0.141.3+1.21.11"
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ github.token }}
|
GITEA_TOKEN: ${{ github.token }}
|
||||||
GITEA_API_URL: ${{ github.server_url }}/api/v1
|
GITEA_API_URL: ${{ github.server_url }}/api/v1
|
||||||
GITEA_REPO: ${{ github.repository }}
|
|
||||||
GITEA_REPO_URL: ${{ github.server_url }}/${{ github.repository }}.git
|
GITEA_REPO_URL: ${{ github.server_url }}/${{ github.repository }}.git
|
||||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
|
||||||
GITEA_SHA: ${{ github.sha }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare source
|
- name: Prepare source
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -39,15 +73,75 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
gradle --no-daemon build
|
|
||||||
|
echo "Building Minecraft ${{ matrix.minecraft_version }}"
|
||||||
|
gradle --no-daemon clean build \
|
||||||
|
-Pminecraft_version="${{ matrix.minecraft_version }}" \
|
||||||
|
-Pyarn_mappings="${{ matrix.yarn_mappings }}" \
|
||||||
|
-Pfabric_version="${{ matrix.fabric_version }}"
|
||||||
|
|
||||||
|
mkdir -p release-artifacts
|
||||||
|
|
||||||
|
jar_path=""
|
||||||
|
shopt -s nullglob
|
||||||
|
for candidate in build/libs/*.jar; do
|
||||||
|
case "$(basename "${candidate}")" in
|
||||||
|
*-sources.jar|*-dev.jar)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
jar_path="${candidate}"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${jar_path}" ]; then
|
||||||
|
echo "No build jar found for Minecraft ${{ matrix.minecraft_version }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "${jar_path}" "release-artifacts/sign-leak-shield-${{ matrix.minecraft_version }}.jar"
|
||||||
|
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sign-leak-shield-${{ matrix.minecraft_version }}
|
||||||
|
path: release-artifacts/sign-leak-shield-${{ matrix.minecraft_version }}.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: build
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: gradle:8.14.5-jdk21
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ github.token }}
|
||||||
|
GITEA_API_URL: ${{ github.server_url }}/api/v1
|
||||||
|
GITEA_REPO: ${{ github.repository }}
|
||||||
|
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||||
|
GITEA_SHA: ${{ github.sha }}
|
||||||
|
steps:
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: versioned-jars
|
||||||
|
pattern: sign-leak-shield-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Publish Gitea release
|
- name: Publish Gitea release
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
jar_path="$(ls -1 build/libs/*.jar | head -n 1)"
|
shopt -s nullglob
|
||||||
|
jar_paths=(versioned-jars/*.jar)
|
||||||
|
if [ "${#jar_paths[@]}" -eq 0 ]; then
|
||||||
|
echo "No build artifacts were downloaded"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||||
release_tag="${GITEA_REF_NAME}"
|
release_tag="${GITEA_REF_NAME}"
|
||||||
release_name="${GITEA_REF_NAME}"
|
release_name="${GITEA_REF_NAME}"
|
||||||
@@ -58,8 +152,8 @@ jobs:
|
|||||||
prerelease=false
|
prerelease=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
release_payload="$(printf '{"tag_name":"%s","name":"%s","body":"Automated build for %s.","draft":false,"prerelease":%s}' \
|
release_payload="$(printf '{"tag_name":"%s","name":"%s","body":"Automated parallel build for Minecraft 1.21.1 through 1.21.11.","draft":false,"prerelease":%s}' \
|
||||||
"${release_tag}" "${release_name}" "${release_name}" "${prerelease}")"
|
"${release_tag}" "${release_name}" "${prerelease}")"
|
||||||
|
|
||||||
release_json="$(curl -fsS \
|
release_json="$(curl -fsS \
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
@@ -74,8 +168,10 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -fsS \
|
for jar_path in "${jar_paths[@]}"; do
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
curl -fsS \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
-F "attachment=@${jar_path}" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
"${GITEA_API_URL}/repos/${GITEA_REPO}/releases/${release_id}/assets?name=$(basename "${jar_path}")"
|
-F "attachment=@${jar_path}" \
|
||||||
|
"${GITEA_API_URL}/repos/${GITEA_REPO}/releases/${release_id}/assets?name=$(basename "${jar_path}")"
|
||||||
|
done
|
||||||
|
|||||||
62
.gitignore
vendored
62
.gitignore
vendored
@@ -1,31 +1,31 @@
|
|||||||
# ---> Java
|
# ---> Java
|
||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
# Log file
|
# Log file
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# BlueJ files
|
# BlueJ files
|
||||||
*.ctxt
|
*.ctxt
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
# Mobile Tools for Java (J2ME)
|
||||||
.mtj.tmp/
|
.mtj.tmp/
|
||||||
|
|
||||||
# Package Files #
|
# Package Files #
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
*.nar
|
*.nar
|
||||||
*.ear
|
*.ear
|
||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
replay_pid*
|
replay_pid*
|
||||||
|
|
||||||
.gradle/*
|
.gradle/*
|
||||||
.gradle/
|
.gradle/
|
||||||
build/*
|
build/*
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
>
|
>
|
||||||
> This mod was built with the help of AI.
|
> This mod was built with the help of AI.
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
This is a Fabric client mod for Minecraft 1.21.1 that defensively patches forced sign-editor translation and keybind leak probes.
|

|
||||||
|
|
||||||
|
This is a Fabric client mod for Minecraft 1.21.11 that defensively patches forced sign-editor translation and keybind leak probes.
|
||||||
|
|
||||||
Bypass's Anti Mod systems on Donut SMP and others like it.
|
Bypass's Anti Mod systems on Donut SMP and others like it.
|
||||||
|
|
||||||
@@ -20,5 +21,5 @@ Build:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- The project targets Fabric for Minecraft 1.21.1.
|
- The project targets Fabric for Minecraft 1.21.11.
|
||||||
- The implementation is scoped to forced sign editor traffic and only rewrites matching outgoing sign update packets.
|
- The implementation is scoped to forced sign editor traffic and only rewrites matching outgoing sign update packets.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ plugins {
|
|||||||
version = project.mod_version
|
version = project.mod_version
|
||||||
group = project.maven_group
|
group = project.maven_group
|
||||||
def modVersion = project.version.toString()
|
def modVersion = project.version.toString()
|
||||||
|
def minecraftVersion = project.minecraft_version.toString()
|
||||||
|
|
||||||
base {
|
base {
|
||||||
archivesName = project.archives_base_name
|
archivesName = project.archives_base_name
|
||||||
@@ -40,9 +41,13 @@ dependencies {
|
|||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
inputs.property "version", modVersion
|
inputs.property "version", modVersion
|
||||||
|
inputs.property "minecraft_version", minecraftVersion
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
filesMatching("fabric.mod.json") {
|
||||||
expand "version": modVersion
|
expand([
|
||||||
|
"version": modVersion,
|
||||||
|
"minecraft_version": minecraftVersion
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ org.gradle.jvmargs=-Xmx2G -Dfile.encoding=UTF-8
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=false
|
||||||
|
|
||||||
minecraft_version=1.21.1
|
minecraft_version=1.21.11
|
||||||
yarn_mappings=1.21.1+build.3
|
yarn_mappings=1.21.11+build.4
|
||||||
loader_version=0.18.4
|
loader_version=0.18.4
|
||||||
loom_version=1.9.1
|
loom_version=1.17.3
|
||||||
fabric_version=0.116.8+1.21.1
|
fabric_version=0.141.3+1.21.11
|
||||||
|
|
||||||
mod_version=1.0.1
|
mod_version=1.0.2
|
||||||
maven_group=com.example
|
maven_group=com.example
|
||||||
archives_base_name=sign-leak-shield
|
archives_base_name=sign-leak-shield
|
||||||
|
|||||||
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package com.example.signleakshield;
|
package com.example.signleakshield;
|
||||||
|
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public final class SignLeakShieldClient implements ClientModInitializer {
|
public final class SignLeakShieldClient implements ClientModInitializer {
|
||||||
public static final String MOD_ID = "signleakshield";
|
public static final String MOD_ID = "signleakshield";
|
||||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
SignLeakShieldTraceLog.reset();
|
SignLeakShieldTraceLog.reset();
|
||||||
SignLeakShieldTraceLog.info("Sign Leak Shield initialized");
|
SignLeakShieldTraceLog.info("Sign Leak Shield initialized");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,58 @@
|
|||||||
package com.example.signleakshield;
|
package com.example.signleakshield;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
public final class SignLeakShieldTraceLog {
|
public final class SignLeakShieldTraceLog {
|
||||||
private static final Path LOG_PATH = Paths.get("logs", "signleakshield-trace.log");
|
private static final Path LOG_PATH = Paths.get("logs", "signleakshield-trace.log");
|
||||||
|
|
||||||
private SignLeakShieldTraceLog() {
|
private SignLeakShieldTraceLog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void reset() {
|
public static synchronized void reset() {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(LOG_PATH);
|
Files.deleteIfExists(LOG_PATH);
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(String message) {
|
public static void info(String message) {
|
||||||
SignLeakShieldClient.LOGGER.info(message);
|
SignLeakShieldClient.LOGGER.info(message);
|
||||||
append(message);
|
append(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(String format, Object... args) {
|
public static void info(String format, Object... args) {
|
||||||
info(String.format(format, args));
|
info(String.format(format, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void append(String message) {
|
private static synchronized void append(String message) {
|
||||||
try {
|
try {
|
||||||
Path parent = LOG_PATH.getParent();
|
Path parent = LOG_PATH.getParent();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
Files.createDirectories(parent);
|
Files.createDirectories(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
String line = String.format(
|
String line = String.format(
|
||||||
"%s %s%n",
|
"%s %s%n",
|
||||||
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now()),
|
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now()),
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
|
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
LOG_PATH,
|
LOG_PATH,
|
||||||
line,
|
line,
|
||||||
StandardCharsets.UTF_8,
|
StandardCharsets.UTF_8,
|
||||||
StandardOpenOption.CREATE,
|
StandardOpenOption.CREATE,
|
||||||
StandardOpenOption.APPEND,
|
StandardOpenOption.APPEND,
|
||||||
StandardOpenOption.WRITE
|
StandardOpenOption.WRITE
|
||||||
);
|
);
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,86 @@
|
|||||||
package com.example.signleakshield;
|
package com.example.signleakshield;
|
||||||
|
|
||||||
import net.minecraft.text.KeybindTextContent;
|
import net.minecraft.text.KeybindTextContent;
|
||||||
import net.minecraft.text.PlainTextContent;
|
import net.minecraft.text.PlainTextContent;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.text.TextContent;
|
import net.minecraft.text.TextContent;
|
||||||
import net.minecraft.text.TranslatableTextContent;
|
import net.minecraft.text.TranslatableTextContent;
|
||||||
|
|
||||||
public final class TextSanitizer {
|
public final class TextSanitizer {
|
||||||
private static final String[] BLACKLISTED_KEY_PARTS = {
|
private static final String[] SUSPICIOUS_KEY_PARTS = {
|
||||||
"gey.glazed.",
|
"gey.glazed.",
|
||||||
|
"key.meteor-client.",
|
||||||
|
"meteor-client",
|
||||||
"meteorline",
|
"meteorline",
|
||||||
"meteorclient",
|
"meteorclient",
|
||||||
"metiorclient",
|
"metiorclient",
|
||||||
|
"meteordevelopment.meteorclient",
|
||||||
"itemscroller",
|
"itemscroller",
|
||||||
"moremousetweaks",
|
"moremousetweaks",
|
||||||
"invmove",
|
"invmove",
|
||||||
"autototem",
|
"autototem",
|
||||||
"smartoffhand",
|
"smartoffhand",
|
||||||
"freecam",
|
"freecam",
|
||||||
"jsmacros",
|
"jsmacros",
|
||||||
"inventoryprofiles",
|
"inventoryprofiles",
|
||||||
"tweakeroo",
|
"tweakeroo",
|
||||||
"soundboard",
|
"soundboard",
|
||||||
"accurateblockplacement"
|
"accurateblockplacement"
|
||||||
};
|
};
|
||||||
|
|
||||||
private TextSanitizer() {
|
private TextSanitizer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSuspicious(Text text) {
|
public static boolean isSuspicious(Text text) {
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextContent content = text.getContent();
|
TextContent content = text.getContent();
|
||||||
|
|
||||||
if (content instanceof TranslatableTextContent translatable) {
|
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) {
|
if (content instanceof KeybindTextContent keybind) {
|
||||||
return isBlacklistedKey(keybind.getKey());
|
String key = keybind.getKey();
|
||||||
|
return key != null && !VanillaKeybinds.isVanillaKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Text sibling : text.getSiblings()) {
|
for (Text sibling : text.getSiblings()) {
|
||||||
if (isSuspicious(sibling)) {
|
if (isSuspicious(sibling)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String sanitize(Text text) {
|
public static String sanitize(Text text) {
|
||||||
StringBuilder out = new StringBuilder();
|
StringBuilder out = new StringBuilder();
|
||||||
append(text, out);
|
append(text, out);
|
||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void append(Text text, StringBuilder out) {
|
private static void append(Text text, StringBuilder out) {
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextContent content = text.getContent();
|
TextContent content = text.getContent();
|
||||||
|
|
||||||
if (content instanceof PlainTextContent plain) {
|
if (content instanceof PlainTextContent plain) {
|
||||||
out.append(plain.string());
|
out.append(plain.string());
|
||||||
} else if (content instanceof TranslatableTextContent translatable) {
|
} else if (content instanceof TranslatableTextContent translatable) {
|
||||||
String fallback = translatable.getFallback();
|
String fallback = translatable.getFallback();
|
||||||
if (fallback != null && !fallback.isEmpty()) {
|
if (fallback != null && !fallback.isEmpty()) {
|
||||||
@@ -75,23 +89,24 @@ public final class TextSanitizer {
|
|||||||
out.append(translatable.getKey());
|
out.append(translatable.getKey());
|
||||||
}
|
}
|
||||||
} else if (content instanceof KeybindTextContent keybind) {
|
} else if (content instanceof KeybindTextContent keybind) {
|
||||||
out.append(text.getString());
|
String key = keybind.getKey();
|
||||||
|
out.append(key != null ? key : "");
|
||||||
} else {
|
} else {
|
||||||
out.append(text.getString());
|
out.append(text.getString());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Text sibling : text.getSiblings()) {
|
for (Text sibling : text.getSiblings()) {
|
||||||
append(sibling, out);
|
append(sibling, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isBlacklistedKey(String key) {
|
private static boolean isBlacklistedKey(String key) {
|
||||||
if (key == null || key.isEmpty()) {
|
if (key == null || key.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String lower = key.toLowerCase();
|
String lower = key.toLowerCase();
|
||||||
for (String part : BLACKLISTED_KEY_PARTS) {
|
for (String part : SUSPICIOUS_KEY_PARTS) {
|
||||||
if (lower.contains(part)) {
|
if (lower.contains(part)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -99,4 +114,36 @@ public final class TextSanitizer {
|
|||||||
|
|
||||||
return false;
|
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,121 @@
|
|||||||
package com.example.signleakshield.mixin;
|
package com.example.signleakshield.mixin;
|
||||||
|
|
||||||
import com.example.signleakshield.ExploitState;
|
import com.example.signleakshield.ExploitState;
|
||||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||||
import com.example.signleakshield.TextSanitizer;
|
import com.example.signleakshield.TextSanitizer;
|
||||||
import net.minecraft.network.ClientConnection;
|
import net.minecraft.network.ClientConnection;
|
||||||
import net.minecraft.network.PacketCallbacks;
|
import net.minecraft.network.PacketCallbacks;
|
||||||
import net.minecraft.network.packet.Packet;
|
import net.minecraft.network.packet.Packet;
|
||||||
import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
|
import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Mixin(ClientConnection.class)
|
@Mixin(ClientConnection.class)
|
||||||
public abstract class ClientConnectionMixin {
|
public abstract class ClientConnectionMixin {
|
||||||
@ModifyVariable(method = "send(Lnet/minecraft/network/packet/Packet;)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) {
|
private Packet<?> signleakshield$rewriteOutgoing(Packet<?> packet) {
|
||||||
if (packet instanceof UpdateSignC2SPacket signPacket) {
|
if (packet instanceof UpdateSignC2SPacket signPacket) {
|
||||||
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
|
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
|
||||||
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(signPacket.getPos());
|
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(signPacket.getPos());
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet observed: pos=%s front=%s forcedOpen=%s captured=%s packetText=%s",
|
"Outgoing sign packet observed: pos=%s front=%s forcedOpen=%s captured=%s packetText=%s",
|
||||||
signPacket.getPos(),
|
signPacket.getPos(),
|
||||||
signPacket.isFront(),
|
signPacket.isFront(),
|
||||||
forcedOpen == null ? "null" : forcedOpen.pos() + "/front=" + forcedOpen.front() + "/ageMs=" + (System.currentTimeMillis() - forcedOpen.timeMs()),
|
forcedOpen == null ? "null" : forcedOpen.pos() + "/front=" + forcedOpen.front() + "/ageMs=" + (System.currentTimeMillis() - forcedOpen.timeMs()),
|
||||||
captured == null ? "null" : "suspicious=" + captured.isSuspicious(),
|
captured == null ? "null" : "suspicious=" + captured.isSuspicious(),
|
||||||
Arrays.toString(signPacket.getText())
|
Arrays.toString(signPacket.getText())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(packet instanceof UpdateSignC2SPacket signPacket)) {
|
if (!(packet instanceof UpdateSignC2SPacket signPacket)) {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
|
ExploitState.ForcedOpenContext forcedOpen = ExploitState.pendingForcedOpen;
|
||||||
if (forcedOpen == null) {
|
if (forcedOpen == null) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet not rewritten: no pending forced-open context for pos=%s front=%s",
|
"Outgoing sign packet not rewritten: no pending forced-open context for pos=%s front=%s",
|
||||||
signPacket.getPos(),
|
signPacket.getPos(),
|
||||||
signPacket.isFront()
|
signPacket.isFront()
|
||||||
);
|
);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockPos pos = signPacket.getPos();
|
BlockPos pos = signPacket.getPos();
|
||||||
if (!forcedOpen.pos().equals(pos)) {
|
if (!forcedOpen.pos().equals(pos)) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet not rewritten: pos mismatch packet=%s forcedOpen=%s",
|
"Outgoing sign packet not rewritten: pos mismatch packet=%s forcedOpen=%s",
|
||||||
pos,
|
pos,
|
||||||
forcedOpen.pos()
|
forcedOpen.pos()
|
||||||
);
|
);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forcedOpen.front() != signPacket.isFront()) {
|
if (forcedOpen.front() != signPacket.isFront()) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet not rewritten: front mismatch packet=%s forcedOpen=%s",
|
"Outgoing sign packet not rewritten: front mismatch packet=%s forcedOpen=%s",
|
||||||
signPacket.isFront(),
|
signPacket.isFront(),
|
||||||
forcedOpen.front()
|
forcedOpen.front()
|
||||||
);
|
);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
long ageMs = System.currentTimeMillis() - forcedOpen.timeMs();
|
long ageMs = System.currentTimeMillis() - forcedOpen.timeMs();
|
||||||
if (ageMs < 0L || ageMs > 5000L) {
|
if (ageMs < 0L || ageMs > 5000L) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet not rewritten: forced-open age out of range pos=%s front=%s ageMs=%s",
|
"Outgoing sign packet not rewritten: forced-open age out of range pos=%s front=%s ageMs=%s",
|
||||||
pos,
|
pos,
|
||||||
signPacket.isFront(),
|
signPacket.isFront(),
|
||||||
ageMs
|
ageMs
|
||||||
);
|
);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(pos);
|
ExploitState.CapturedSignData captured = ExploitState.SIGNS.get(pos);
|
||||||
if (captured == null || !captured.isSuspicious()) {
|
if (captured == null || !captured.isSuspicious()) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Outgoing sign packet allowed: captured sign missing or not blacklisted pos=%s front=%s capturedPresent=%s",
|
"Outgoing sign packet allowed: captured sign missing or not blacklisted pos=%s front=%s capturedPresent=%s",
|
||||||
pos,
|
pos,
|
||||||
signPacket.isFront(),
|
signPacket.isFront(),
|
||||||
captured != null
|
captured != null
|
||||||
);
|
);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text[] textLines;
|
Text[] textLines;
|
||||||
textLines = signPacket.isFront() ? captured.getFront() : captured.getBack();
|
textLines = signPacket.isFront() ? captured.getFront() : captured.getBack();
|
||||||
|
String[] originalResponse = signPacket.getText();
|
||||||
|
|
||||||
String line1 = TextSanitizer.sanitize(textLines[0]);
|
String line1 = TextSanitizer.sanitize(textLines[0]);
|
||||||
String line2 = TextSanitizer.sanitize(textLines[1]);
|
String line2 = TextSanitizer.sanitize(textLines[1]);
|
||||||
String line3 = TextSanitizer.sanitize(textLines[2]);
|
String line3 = TextSanitizer.sanitize(textLines[2]);
|
||||||
String line4 = TextSanitizer.sanitize(textLines[3]);
|
String line4 = TextSanitizer.sanitize(textLines[3]);
|
||||||
|
String[] returnedResponse = new String[] { line1, line2, line3, line4 };
|
||||||
|
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Blocked forced sign translation event at %s front=%s: got=%s, returned=%s",
|
"Blocked forced sign translation event at %s front=%s: receivedSign=%s, unmodifiedResponse=%s, returned=%s",
|
||||||
pos,
|
pos,
|
||||||
signPacket.isFront(),
|
signPacket.isFront(),
|
||||||
Arrays.toString(signPacket.getText()),
|
describeTextLines(textLines),
|
||||||
Arrays.toString(new String[] { line1, line2, line3, line4 })
|
Arrays.toString(originalResponse),
|
||||||
|
Arrays.toString(returnedResponse)
|
||||||
);
|
);
|
||||||
|
|
||||||
ExploitState.clearForcedOpen();
|
ExploitState.clearForcedOpen();
|
||||||
return new UpdateSignC2SPacket(pos, signPacket.isFront(), line1, line2, line3, line4);
|
return new UpdateSignC2SPacket(pos, signPacket.isFront(), line1, line2, line3, line4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String describeTextLines(Text[] lines) {
|
||||||
|
String[] values = new String[lines.length];
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
values[i] = lines[i] != null ? lines[i].getString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.toString(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
package com.example.signleakshield.mixin;
|
package com.example.signleakshield.mixin;
|
||||||
|
|
||||||
import com.example.signleakshield.ExploitState;
|
import com.example.signleakshield.ExploitState;
|
||||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||||
import com.example.signleakshield.SignTextExtractor;
|
import com.example.signleakshield.SignTextExtractor;
|
||||||
import net.minecraft.block.entity.BlockEntityType;
|
import net.minecraft.block.entity.BlockEntityType;
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
|
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
public abstract class ClientPlayNetworkHandlerBlockEntityUpdateMixin {
|
public abstract class ClientPlayNetworkHandlerBlockEntityUpdateMixin {
|
||||||
@Inject(method = "onBlockEntityUpdate(Lnet/minecraft/network/packet/s2c/play/BlockEntityUpdateS2CPacket;)V", 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) {
|
private void signleakshield$captureSign(BlockEntityUpdateS2CPacket packet, CallbackInfo ci) {
|
||||||
if (packet.getNbt() == null) {
|
if (packet.getNbt() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.getBlockEntityType() != BlockEntityType.SIGN && packet.getBlockEntityType() != BlockEntityType.HANGING_SIGN) {
|
if (packet.getBlockEntityType() != BlockEntityType.SIGN && packet.getBlockEntityType() != BlockEntityType.HANGING_SIGN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockPos pos = packet.getPos();
|
BlockPos pos = packet.getPos();
|
||||||
ExploitState.SIGNS.put(pos.toImmutable(), SignTextExtractor.fromNbt(packet.getNbt()));
|
ExploitState.SIGNS.put(pos.toImmutable(), SignTextExtractor.fromNbt(packet.getNbt()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
package com.example.signleakshield.mixin;
|
package com.example.signleakshield.mixin;
|
||||||
|
|
||||||
import com.example.signleakshield.ExploitState;
|
import com.example.signleakshield.ExploitState;
|
||||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||||
import com.example.signleakshield.SignTextExtractor;
|
import com.example.signleakshield.SignTextExtractor;
|
||||||
import net.minecraft.block.entity.BlockEntityType;
|
import net.minecraft.block.entity.BlockEntityType;
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
|
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
|
||||||
import net.minecraft.nbt.NbtCompound;
|
import net.minecraft.nbt.NbtCompound;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
public abstract class ClientPlayNetworkHandlerChunkDataMixin {
|
public abstract class ClientPlayNetworkHandlerChunkDataMixin {
|
||||||
@Inject(method = "onChunkData(Lnet/minecraft/network/packet/s2c/play/ChunkDataS2CPacket;)V", 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) {
|
private void signleakshield$captureChunkData(ChunkDataS2CPacket packet, CallbackInfo ci) {
|
||||||
packet.getChunkData().getBlockEntities(packet.getChunkX(), packet.getChunkZ()).accept((localPos, type, nbt) -> {
|
packet.getChunkData().getBlockEntities(packet.getChunkX(), packet.getChunkZ()).accept((localPos, type, nbt) -> {
|
||||||
if (nbt == null) {
|
if (nbt == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != BlockEntityType.SIGN && type != BlockEntityType.HANGING_SIGN) {
|
if (type != BlockEntityType.SIGN && type != BlockEntityType.HANGING_SIGN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockPos pos = localPos.toImmutable();
|
BlockPos pos = localPos.toImmutable();
|
||||||
ExploitState.SIGNS.put(pos, SignTextExtractor.fromNbt((NbtCompound) nbt));
|
ExploitState.SIGNS.put(pos, SignTextExtractor.fromNbt((NbtCompound) nbt));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package com.example.signleakshield.mixin;
|
package com.example.signleakshield.mixin;
|
||||||
|
|
||||||
import com.example.signleakshield.ExploitState;
|
import com.example.signleakshield.ExploitState;
|
||||||
import com.example.signleakshield.SignLeakShieldTraceLog;
|
import com.example.signleakshield.SignLeakShieldTraceLog;
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
import net.minecraft.network.packet.s2c.play.SignEditorOpenS2CPacket;
|
import net.minecraft.network.packet.s2c.play.SignEditorOpenS2CPacket;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
@Mixin(ClientPlayNetworkHandler.class)
|
||||||
public abstract class ClientPlayNetworkHandlerSignEditorOpenMixin {
|
public abstract class ClientPlayNetworkHandlerSignEditorOpenMixin {
|
||||||
@Inject(method = "onSignEditorOpen", at = @At("HEAD"))
|
@Inject(method = "onSignEditorOpen", at = @At("HEAD"))
|
||||||
private void signleakshield$rememberForcedOpen(SignEditorOpenS2CPacket packet, CallbackInfo ci) {
|
private void signleakshield$rememberForcedOpen(SignEditorOpenS2CPacket packet, CallbackInfo ci) {
|
||||||
SignLeakShieldTraceLog.info(
|
SignLeakShieldTraceLog.info(
|
||||||
"Sign editor open received: pos=%s front=%s",
|
"Sign editor open received: pos=%s front=%s",
|
||||||
packet.getPos(),
|
packet.getPos(),
|
||||||
packet.isFront()
|
packet.isFront()
|
||||||
);
|
);
|
||||||
ExploitState.rememberForcedOpen(packet.getPos(), packet.isFront());
|
ExploitState.rememberForcedOpen(packet.getPos(), packet.isFront());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.18.4",
|
"fabricloader": ">=0.18.4",
|
||||||
"minecraft": "1.21.1",
|
"minecraft": "${minecraft_version}",
|
||||||
"java": ">=21",
|
"java": ">=21",
|
||||||
"fabric-api": "*"
|
"fabric-api": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user