From 1c789111e803340589150249d6ab482a65a4a7f2 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:13:29 -0500 Subject: [PATCH 1/4] "Fix" Hypixel's disappearing chests problem on modern clients We really should not need to do this. Hypixel, *please* fix your protocol translation plugin. --- .../LevelChunkMixin.java | 97 +++++++++++++++++++ .../core/config/ModernFixEarlyConfig.java | 1 + .../assets/modernfix/lang/en_us.json | 3 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java new file mode 100644 index 00000000..e5c92256 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java @@ -0,0 +1,97 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.missing_block_entities; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Hypixel (and possibly other buggy servers) send chunks to the client that are missing some block entity data, which + * causes these entities to be invisible. We "fix" this by recreating the block entity on the client with default data, + * which is hopefully what the legacy server also expects. + */ +@Mixin(LevelChunk.class) +@ClientOnlyMixin +public abstract class LevelChunkMixin extends ChunkAccess { + @Shadow @Final private Level level; + + @Shadow @Nullable public abstract BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType); + + public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) { + super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData); + } + + @Inject(method = "replaceWithPacketData", at = @At("RETURN")) + private void validateBlockEntitiesInChunk(CallbackInfo ci) { + // No reason to check in singleplayer or on the integrated server + if (this.level.isClientSide && !Minecraft.getInstance().isLocalServer()) { + for (int i = 0; i < this.sections.length; i++) { + var section = this.sections[i]; + try { + if (!section.hasOnlyAir() && section.maybeHas(BlockBehaviour.BlockStateBase::hasBlockEntity)) { + scanSectionForBlockEntities(section, i); + } + } catch(Exception e) { + ModernFix.LOGGER.error("Exception validating data in chunk", e); + return; + } + } + } + } + + @Unique + private void scanSectionForBlockEntities(LevelChunkSection section, int i) { + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + int sectionY = this.getSectionYFromSectionIndex(i); + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + var state = section.getBlockState(x, y, z); + if (state.hasBlockEntity()) { + cursor.set(chunkX * 16 + x, sectionY * 16 + y, chunkZ * 16 + z); + makeBlockEntityIfNotExists(state, cursor); + } + } + } + } + } + + @Unique + private void makeBlockEntityIfNotExists(BlockState state, BlockPos.MutableBlockPos pos) { + if (this.blockEntities.containsKey(pos) || this.pendingBlockEntities.containsKey(pos)) { + return; + } + + BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE); + String blockName = state.getBlock().toString(); + if (blockEntity != null) { + ModernFix.LOGGER.warn("Created missing block entity for {} at {}", blockName, pos.toShortString()); + } else { + ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString()); + } + } +} + diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 85d4f032..c7814e3a 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -167,6 +167,7 @@ public class ModernFixEarlyConfig { .put("mixin.bugfix.restore_old_dragon_movement", false) .put("mixin.perf.worldgen_allocation", false) // experimental .put("mixin.feature.cause_lag_by_disabling_threads", false) + .put("mixin.bugfix.missing_block_entities", false) .put("mixin.perf.clear_mixin_classinfo", false) .put("mixin.perf.deduplicate_climate_parameters", false) .put("mixin.bugfix.packet_leak", false) diff --git a/common/src/main/resources/assets/modernfix/lang/en_us.json b/common/src/main/resources/assets/modernfix/lang/en_us.json index 7ab5e016..e18097bc 100644 --- a/common/src/main/resources/assets/modernfix/lang/en_us.json +++ b/common/src/main/resources/assets/modernfix/lang/en_us.json @@ -134,5 +134,6 @@ "modernfix.option.mixin.perf.fix_loop_spin_waiting": "Fixes Minecraft's built-in wait function consuming excessive amounts of CPU resources.", "modernfix.option.mixin.perf.forge_cap_retrieval": "Small micro-optimization that makes retrieving custom entity data slightly more efficient on Forge.", "modernfix.option.mixin.perf.forge_registry_lambda": "Fixes oversights in Forge that lead to excessive allocation in hot registry methods.", - "modernfix.option.mixin.bugfix.restore_old_dragon_movement": "Fixes MC-272431, which tracks the ender dragon being unable to dive to the portal as it did in 1.13 and older. This causes the dragon to fly quite a bit differently from what modern players are used to and also patches out one-cycling, so it's not enabled by default. Thanks to Jukitsu for identifying the regression in the vanilla code." + "modernfix.option.mixin.bugfix.restore_old_dragon_movement": "Fixes MC-272431, which tracks the ender dragon being unable to dive to the portal as it did in 1.13 and older. This causes the dragon to fly quite a bit differently from what modern players are used to and also patches out one-cycling, so it's not enabled by default. Thanks to Jukitsu for identifying the regression in the vanilla code.", + "modernfix.option.mixin.bugfix.missing_block_entities": "Hypixel sends chunks to the client that are missing some block entity data, which makes chests etc. appear invisible. This 'fixes' the problem by creating the needed data on the client. Has no effect for properly behaved servers or in singleplayer." } From 32a8800344876ed289e1f4fd911b101d41f7b876 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:27:56 -0500 Subject: [PATCH 2/4] Premultiply chunk offset for incredibly small performance gain --- .../bugfix/missing_block_entities/LevelChunkMixin.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java index e5c92256..923ca57c 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java @@ -62,16 +62,16 @@ public abstract class LevelChunkMixin extends ChunkAccess { @Unique private void scanSectionForBlockEntities(LevelChunkSection section, int i) { - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; + int chunkXOff = this.chunkPos.x * 16; + int chunkZOff = this.chunkPos.z * 16; BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); - int sectionY = this.getSectionYFromSectionIndex(i); + int sectionYOff = this.getSectionYFromSectionIndex(i) * 16; for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { var state = section.getBlockState(x, y, z); if (state.hasBlockEntity()) { - cursor.set(chunkX * 16 + x, sectionY * 16 + y, chunkZ * 16 + z); + cursor.set(chunkXOff + x, sectionYOff + y, chunkZOff + z); makeBlockEntityIfNotExists(state, cursor); } } From 64a427fa62a9d31c9d0851d4952778f2071e6c08 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:51:20 -0500 Subject: [PATCH 3/4] Add LDLib connected textures integration --- forge/build.gradle | 1 + .../ldlib/ClientProxyImplMixin.java | 102 ++++++++++++++++++ gradle.properties | 1 + 3 files changed, 104 insertions(+) create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java diff --git a/forge/build.gradle b/forge/build.gradle index 101dba1a..4d5881ec 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -56,6 +56,7 @@ dependencies { modCompileOnly("dev.latvian.mods:kubejs-forge:${kubejs_version}") //modRuntimeOnly("curse.maven:ferritecore-429235:4441949") modCompileOnly("curse.maven:ctm-267602:${ctm_version}") + modCompileOnly("curse.maven:ldlib-626676:${ldlib_version}") modCompileOnly("curse.maven:supermartijncore-454372:4455391") modCompileOnly("vazkii.patchouli:Patchouli:1.19.2-77") diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java new file mode 100644 index 00000000..de4e80db --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java @@ -0,0 +1,102 @@ +package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources.ldlib; + +import com.lowdragmc.lowdraglib.LDLib; +import com.lowdragmc.lowdraglib.client.ClientProxy; +import com.lowdragmc.lowdraglib.client.forge.ClientProxyImpl; +import com.lowdragmc.lowdraglib.client.model.custommodel.LDLMetadataSection; +import com.lowdragmc.lowdraglib.client.model.forge.CustomBakedModelImpl; +import com.lowdragmc.lowdraglib.client.model.forge.LDLRendererModel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import org.embeddedt.modernfix.ModernFixClient; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.annotation.RequiresMod; +import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +@Mixin(ClientProxyImpl.class) +@ClientOnlyMixin +@RequiresMod("ldlib") +public abstract class ClientProxyImplMixin implements ModernFixClientIntegration { + @Inject(method = "", at = @At("RETURN")) + private void registerIntegration(CallbackInfo ci) { + ModernFixClient.CLIENT_INTEGRATIONS.add(this); + } + + @Redirect(method = "modelBake", at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;", ordinal = 0), remap = false) + private Set disableLoop(Map map) { + return Set.of(); + } + + @Override + public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, Function textureGetter) { + if (baked == null) { + return null; + } + if (rl instanceof ModelResourceLocation && rootModel != null) { + if (baked instanceof LDLRendererModel) { + return baked; + } + if (baked.isCustomRenderer()) { // Nothing we can add to builtin models + return baked; + } + Deque dependencies = new ArrayDeque<>(); + Set seenModels = new HashSet<>(); + dependencies.push(rl); + seenModels.add(rl); + boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(rl, false); + // Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles + while (!shouldWrap && !dependencies.isEmpty()) { + ResourceLocation dep = dependencies.pop(); + UnbakedModel model; + try { + model = dep == rl ? rootModel : bakery.getModel(dep); + } catch (Exception e) { + continue; + } + try { + Set textures = new HashSet<>(ClientProxy.SCRAPED_TEXTURES.get(dep)); + for (Material tex : textures) { + // Cache all dependent texture metadata + // At least one texture has CTM metadata, so we should wrap this baked + if (!LDLMetadataSection.getMetadata(LDLMetadataSection.spriteToAbsolute(tex.texture())).isMissing()) { // TODO lazy + shouldWrap = true; + break; + } + } + if (!shouldWrap) { + for (ResourceLocation newDep : model.getDependencies()) { + if (seenModels.add(newDep)) { + dependencies.push(newDep); + } + } + } + } catch (Exception e) { + LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e); + } + } + ClientProxy.WRAPPED_MODELS.put(rl, shouldWrap); + if (shouldWrap) { + return new CustomBakedModelImpl(baked); + } + } + return baked; + } +} diff --git a/gradle.properties b/gradle.properties index 3c239fa4..77d5cc8e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,6 +13,7 @@ refined_storage_version=4392788 jei_version=15.8.0.11 rei_version=11.0.597 ctm_version=5983309 +ldlib_version=5927130 kubejs_version=1902.6.0-build.142 rhino_version=1902.2.2-build.268 supported_minecraft_versions=1.20.1 From b72959f257480258cc869897d5ab32f2612a3c55 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 26 Dec 2024 15:17:54 -0500 Subject: [PATCH 4/4] Block telemetry by default Related: #488 --- .../ClientTelemetryManagerMixin.java | 22 +++++++++++++++++++ .../MinecraftMixin_Telemetry.java | 17 ++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java create mode 100644 common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java new file mode 100644 index 00000000..a890c749 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.common.mixin.feature.remove_telemetry; + +import net.minecraft.client.telemetry.ClientTelemetryManager; +import net.minecraft.client.telemetry.TelemetryEventSender; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +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; + +@Mixin(value = ClientTelemetryManager.class, priority = 1500) +@ClientOnlyMixin +public class ClientTelemetryManagerMixin { + /** + * @author embeddedt + * @reason telemetry is useless noise for modded instances anyway, and introduces privacy concerns + */ + @Inject(method = "createEventSender", at = @At("HEAD"), cancellable = true) + private void disableTelemetrySender(CallbackInfoReturnable cir) { + cir.setReturnValue(TelemetryEventSender.DISABLED); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java new file mode 100644 index 00000000..e3fa63b8 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.common.mixin.feature.remove_telemetry; + +import net.minecraft.client.Minecraft; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +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; + +@Mixin(value = Minecraft.class, priority = 1100) +@ClientOnlyMixin +public class MinecraftMixin_Telemetry { + @Inject(method = "allowsTelemetry", at = @At("HEAD"), cancellable = true) + private void markTelemetryNotAllowed(CallbackInfoReturnable cir) { + cir.setReturnValue(false); + } +}