diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/SLSCarpetSettings.java b/src/main/java/com/github/zly2006/carpetslsaddition/SLSCarpetSettings.java index 6fa7e39..16df104 100644 --- a/src/main/java/com/github/zly2006/carpetslsaddition/SLSCarpetSettings.java +++ b/src/main/java/com/github/zly2006/carpetslsaddition/SLSCarpetSettings.java @@ -7,6 +7,7 @@ public class SLSCarpetSettings { public static final String NEED_CLIENT = "needClient"; // 需要客户端安装SLS-Addition或实现相关支持 public static final String FROM_AMS = "AMS"; public static final String SLSA = "SLS"; + public static final String LEGACY = "旧版"; @Rule(categories = {SLSA, RuleCategory.SURVIVAL}) public static boolean obtainableReinforcedDeepSlate = false; @@ -77,4 +78,32 @@ public class SLSCarpetSettings { @Rule(categories = {SLSA, RuleCategory.FEATURE}) public static boolean unbreakableDecoratedPot = false; + // ===== [旧版] 遗留漏洞机制复刻 ===== + // 总开关:开启后,其余子规则才生效 + @Rule(categories = {LEGACY}, strict = false) + public static boolean legacyExploitMode = false; + + // 等价 instantTick:计划刻(scheduleTick)立即执行,不等延迟 + @Rule(categories = {LEGACY}) + public static boolean legacyInstantTick = false; + + // 等价 instantFall:重力方块被计划刻触发时立即下落 + @Rule(categories = {LEGACY}) + public static boolean legacyInstantFall = false; + + // 自建后台线程,模拟 1.12 信标线程并发访问 chunk + @Rule(categories = {LEGACY}) + public static boolean legacyAsyncChunkAccess = false; + + // 允许异步线程在 chunk 表竞态时把"已加载"当成"未加载",触发重新加载/装饰 + @Rule(categories = {LEGACY}) + public static boolean legacyChunkSwap = false; + + // 落沙替换:在 FallingBlock 转实体窗口内把方块替换成目标方块 + @Rule(categories = {LEGACY}) + public static boolean legacyFallingBlockReplace = false; + + // 目标方块(替换结果),默认末地传送门框架 + @Rule(categories = {LEGACY}, strict = false, options = {"#none", "end_portal_frame", "command_block", "spawner", "barrier", "nether_portal"}) + public static String legacyFallingBlockTarget = "#none"; } diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java b/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java index 2e289f7..c41a93f 100644 --- a/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java +++ b/src/main/java/com/github/zly2006/carpetslsaddition/ServerMain.java @@ -51,6 +51,34 @@ public class ServerMain implements ModInitializer, CarpetExtension { @Override public void onServerLoaded(MinecraftServer server) { ServerMain.server = server; + startLegacyRuleWatcher(); + } + + /** [旧版] 轮询规则状态,按需启停异步 chunk 线程。 */ + private void startLegacyRuleWatcher() { + Thread watcher = new Thread(() -> { + boolean running = false; + while (!Thread.currentThread().isInterrupted()) { + try { + boolean want = SLSCarpetSettings.legacyExploitMode + && SLSCarpetSettings.legacyAsyncChunkAccess; + if (want && !running) { + com.github.zly2006.carpetslsaddition.util.LegacyAsyncExecutor.start(); + running = true; + } else if (!want && running) { + com.github.zly2006.carpetslsaddition.util.LegacyAsyncExecutor.stop(); + running = false; + } + Thread.sleep(1000L); + } catch (InterruptedException e) { + return; + } catch (Throwable t) { + LOGGER.debug("[SLSA] legacy rule watcher error: {}", t.toString()); + } + } + }, "SLSA-Legacy-RuleWatcher"); + watcher.setDaemon(true); + watcher.start(); } @Override diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ChunkMapMixin.java b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ChunkMapMixin.java new file mode 100644 index 0000000..8b86786 --- /dev/null +++ b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ChunkMapMixin.java @@ -0,0 +1,56 @@ +package com.github.zly2006.carpetslsaddition.mixin.legacy; + +import com.github.zly2006.carpetslsaddition.SLSCarpetSettings; +import com.github.zly2006.carpetslsaddition.ServerMain; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +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.CallbackInfoReturnable; + +/** + * [旧版] 区块替换(chunk swap)——race condition 的等价实现。 + * + *
1.12 的漏洞:{@code ChunkProviderServer.id2ChunkMap}(非线程安全的 + * {@code Long2ObjectOpenHashMap})被主线程 {@code remove}(卸载)与异步线程 {@code get} + * 并发访问,导致 {@code get} 错过已加载的区块,进而触发"重新加载 + 世界装饰"。 + * + *
26.1.2 已用 {@code Long2ObjectLinkedOpenHashMap} + {@code mainThreadExecutor} 约束修复。 + * 直接并发改 map 风险极高(可能崩溃/损坏存档),故本 mixin 采用低风险等价方案: + * hook {@link ChunkMap#getVisibleChunkIfPresent(long)},当满足以下条件时对非主线程调用返回 {@code null}: + *
⚠ 风险:仍可能引起区块状态不一致。仅在明确开启时生效。
+ */
+@Mixin(ChunkMap.class)
+public abstract class ChunkMapMixin {
+
+ @Inject(method = "getVisibleChunkIfPresent", at = @At("RETURN"), cancellable = true)
+ private void legacyChunkSwap(long chunkPos, CallbackInfoReturnable 26.1.2 的 {@code FallingBlockEntity.fall(Level, BlockPos, BlockState)} 直接使用传入的 state,
+ * 不像 1.12 那样在构造实体时重新读取世界。本 mixin 用 {@link ModifyVariable} 拦截该 state 参数:
+ * 当 {@code legacyFallingBlockReplace} 开启且配置了目标方块时,把 state 替换为目标方块的默认状态。
+ *
+ * 等价于 1.12 在"检查可下落"与"读取方块状态"之间的窗口替换效果——最终得到一个
+ * "装着目标方块"的下落实体,落地后即还原为该目标方块。
+ */
+@Mixin(net.minecraft.world.entity.item.FallingBlockEntity.class)
+public class FallingBlockEntityMixin {
+
+ @ModifyVariable(
+ method = "fall",
+ at = @At("HEAD"),
+ argsOnly = true,
+ index = 2
+ )
+ private static BlockState legacyReplaceFallingState(BlockState original) {
+ if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) {
+ return original;
+ }
+ String target = SLSCarpetSettings.legacyFallingBlockTarget;
+ if (target == null || target.isEmpty() || "#none".equals(target)) {
+ return original;
+ }
+ Block block = BuiltInRegistries.BLOCK.getOptional(Identifier.withDefaultNamespace(target)).orElse(null);
+ if (block == null) {
+ return original;
+ }
+ return block.defaultBlockState();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockMixin.java b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockMixin.java
new file mode 100644
index 0000000..c5598ef
--- /dev/null
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockMixin.java
@@ -0,0 +1,47 @@
+package com.github.zly2006.carpetslsaddition.mixin.legacy;
+
+import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
+import com.github.zly2006.carpetslsaddition.util.LegacyExploitState;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.block.FallingBlock;
+import net.minecraft.world.level.block.state.BlockState;
+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;
+
+/**
+ * [旧版] 落沙替换 / 瞬间下落。
+ *
+ * 1.12 的漏洞窗口:BlockFalling 在创建 EntityFallingBlock 时【重新读取世界】的方块状态,
+ * 若在"检查可下落"与"读取状态"之间被异步线程替换,就能得到任意方块的下落实体。
+ *
+ * 26.1.2 已把该窗口结构性消除(fall() 直接接收 state 参数,不重读世界)。
+ * 本 mixin 用规则【重新引入】等价行为:
+ * 1.12 的 instantTick:世界装饰时会短暂开启,使计划刻(中继器、发射器、沙子等)
+ * 立即执行而不等待延迟。26.1.2 已移除该 flag。
+ *
+ * 本 mixin 用 {@link Inject} 拦截 {@link ScheduledTick#triggerTick()}:
+ * 规则开启时返回 0,使该计划刻在"当前时刻"被触发(等价立即执行)。
+ *
+ * 注意:这会强制所有计划刻立即触发,可能造成大量连锁更新,
+ * 仅应在 {@code legacyExploitMode} + {@code legacyInstantTick} 同时开启时使用。
+ */
+@Mixin(ScheduledTick.class)
+public abstract class ScheduledTickMixin {
+
+ @Inject(method = "triggerTick", at = @At("HEAD"), cancellable = true)
+ private void legacyInstantTriggerTick(CallbackInfoReturnable 1.12 的漏洞依赖一个独立的异步线程,与主线程并发访问区块哈希表
+ * ({@code Long2ObjectOpenHashMap}),从而制造 race condition,让 chunk 被误判为"未加载",
+ * 触发重新加载与世界装饰。
+ *
+ * 26.1.2 的信标已无独立线程,故本类自建一个单线程调度器:
+ * 周期性在主线程之外调用 {@link ServerChunkCache#getChunkNow} 与读取区块,
+ * 尽最大可能复现 1.12 的并发访问模式。
+ *
+ * ⚠ 风险提示:26.1.2 的区块访问有 {@code mainThreadExecutor} 约束,
+ * 跨线程访问可能触发断言或未定义行为,极端情况下可导致服务器崩溃或存档损坏。
+ * 仅在 {@code legacyExploitMode} + {@code legacyAsyncChunkAccess} 同时开启时启动。
+ */
+public final class LegacyAsyncExecutor {
+
+ private LegacyAsyncExecutor() {}
+
+ private static ScheduledExecutorService executor;
+ private static volatile boolean started = false;
+
+ /** 目标区块坐标(由玩家装置或命令设置)。 */
+ private static volatile int targetChunkX = Integer.MIN_VALUE;
+ private static volatile int targetChunkZ = Integer.MIN_VALUE;
+
+ public static void setTargetChunk(int x, int z) {
+ targetChunkX = x;
+ targetChunkZ = z;
+ }
+
+ public static synchronized void start() {
+ if (started) {
+ return;
+ }
+ executor = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r, "SLSA-Legacy-AsyncChunk");
+ t.setDaemon(true);
+ return t;
+ });
+ executor.scheduleAtFixedRate(LegacyAsyncExecutor::tick, 0, 1, TimeUnit.MILLISECONDS);
+ started = true;
+ LegacyExploitState.setAsyncThreadRunning(true);
+ ServerMain.LOGGER.info("[SLSA] legacy async chunk thread started");
+ }
+
+ public static synchronized void stop() {
+ if (!started || executor == null) {
+ return;
+ }
+ executor.shutdownNow();
+ executor = null;
+ started = false;
+ LegacyExploitState.setAsyncThreadRunning(false);
+ ServerMain.LOGGER.info("[SLSA] legacy async chunk thread stopped");
+ }
+
+ /** 异步线程主体:并发访问目标区块,制造与主线程的竞态。 */
+ private static void tick() {
+ try {
+ if (!SLSCarpetSettingsBridge.shouldRun()) {
+ return;
+ }
+ MinecraftServer server = ServerMain.server;
+ if (server == null) {
+ return;
+ }
+ for (ServerLevel level : server.getAllLevels()) {
+ ServerChunkCache cache = level.getChunkSource();
+ if (targetChunkX != Integer.MIN_VALUE) {
+ // 故意在主线程之外读取区块表——制造 race condition
+ LevelChunk chunk = cache.getChunkNow(targetChunkX, targetChunkZ);
+ if (chunk != null) {
+ // 触发一次方块状态读取,模拟信标向下搜索
+ chunk.getBlockState(new net.minecraft.core.BlockPos(
+ targetChunkX << 4, level.getMinY(), targetChunkZ << 4));
+ }
+ }
+ }
+ } catch (Throwable t) {
+ // 异步线程绝不能让异常冒泡导致调度器静默停止
+ ServerMain.LOGGER.debug("[SLSA] legacy async tick error: {}", t.toString());
+ }
+ }
+
+ /** 桥接类:避免直接依赖 SLSCarpetSettings 的静态初始化顺序问题。 */
+ private static final class SLSCarpetSettingsBridge {
+ static boolean shouldRun() {
+ return com.github.zly2006.carpetslsaddition.SLSCarpetSettings.legacyExploitMode
+ && com.github.zly2006.carpetslsaddition.SLSCarpetSettings.legacyAsyncChunkAccess;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyExploitState.java b/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyExploitState.java
new file mode 100644
index 0000000..dbe642b
--- /dev/null
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyExploitState.java
@@ -0,0 +1,66 @@
+package com.github.zly2006.carpetslsaddition.util;
+
+import net.minecraft.core.BlockPos;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * [旧版] 漏洞机制的全局状态管理(线程安全)。
+ *
+ * 承载:
+ * 线程安全说明:本类会被主线程与自建的异步线程同时访问,
+ * 所有可变状态均使用并发容器或原子引用。
+ */
+public final class LegacyExploitState {
+
+ private LegacyExploitState() {}
+
+ /** 当前落沙替换目标(方块 ID 字符串),"#none" 表示禁用。 */
+ private static final AtomicReference
+ *
+ */
+@Mixin(FallingBlock.class)
+public class FallingBlockMixin {
+
+ // instantFall 等价:在 tick 入口,若规则开启,直接执行"立即下落"分支。
+ // 26.1.2 的 tick 已是计划刻回调,这里仅在规则开启时额外触发一次立即下落逻辑。
+ @Inject(method = "tick", at = @At("HEAD"), cancellable = true)
+ private void legacyTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand, CallbackInfo ci) {
+ if (!SLSCarpetSettings.legacyExploitMode) {
+ return;
+ }
+ if (SLSCarpetSettings.legacyInstantFall) {
+ // 标记:本次下落由 instantFall 触发(供替换逻辑判断窗口)
+ LegacyExploitState.markInstantFall(pos);
+ }
+ if (SLSCarpetSettings.legacyFallingBlockReplace) {
+ // 由 FallingBlockEntityMixin.fall 处接管状态替换
+ LegacyExploitState.setReplaceTarget(SLSCarpetSettings.legacyFallingBlockTarget);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ScheduledTickMixin.java b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ScheduledTickMixin.java
new file mode 100644
index 0000000..ac0773c
--- /dev/null
+++ b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/ScheduledTickMixin.java
@@ -0,0 +1,32 @@
+package com.github.zly2006.carpetslsaddition.mixin.legacy;
+
+import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
+import net.minecraft.world.ticks.ScheduledTick;
+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.CallbackInfoReturnable;
+
+/**
+ * [旧版] 计划刻立即执行(等价 1.12 的 instantTick flag)。
+ *
+ *
+ *
+ *
+ *