Build for Minecraft 26.1

Adds 26.1 as a Stonecutter version (step 3 of #39). Its jar also
loads on the 26.1.1 and 26.1.2 hotfixes.

Most of 26.1's changes are renames: the GUI's render methods became
extract methods and GuiGraphics became GuiGraphicsExtractor, which
Stonecutter replacements cover along with the moved render state
package and GuiRenderState's add methods. The rest are version
comments:

- The menu panorama is drawn by GuiRenderer from vanilla's cube map;
  the new GuiRendererMixin swaps in Saturn's while a Saturn screen is
  open (empty on older versions).
- The camera works out the FOV and aligns with its entity in
  alignWithEntity, so the zoom and freelook hooks move to CameraMixin.
- Gamma and the menu blur are read while extracting render state.
- The glass blur's post chain takes a Projection and its buffer.
- The dragon's alpha render type is gone; capes use entityCutout.
- Fabric API renamed KeyBindingHelper to KeyMappingHelper.

The docs describe the 26.x build and Java 25.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-30 19:15:10 +02:00
co-authored by claude
parent b018db1851
commit 4fb48cc647
18 changed files with 217 additions and 35 deletions
@@ -88,7 +88,10 @@ public class ShaderUtils {
}
private static RenderType entityAlpha(Identifier texture) {
//? if >=1.21.11 {
// 26.1 dropped the dragon's alpha pipeline; with opaque vertices it's an alpha cutout.
//? if >=26.1 {
/*return RenderTypes.entityCutout(texture);
*///?} else if >=1.21.11 {
return RenderTypes.dragonExplosionAlpha(texture);
//?} else
//return RenderType.dragonExplosionAlpha(texture);
@@ -1,6 +1,7 @@
package org.saturnclient.impl.features.mixins.entity;
import org.saturnclient.impl.features.entity.CameraOverriddenEntity;
import org.saturnclient.impl.features.render.ZoomFov;
import org.saturnclient.mod.mods.FreelookMod;
import org.saturnclient.saturnclient.SaturnClient;
import org.spongepowered.asm.mixin.Mixin;
@@ -9,6 +10,8 @@ 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;
//? if >=26.1
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.Camera;
@@ -26,17 +29,38 @@ public abstract class CameraMixin {
@Shadow
protected abstract void setRotation(float yaw, float pitch);
// Freelook keeps the camera's rotation after vanilla points it along the entity's view. From 26.1
// that happens in alignWithEntity, which takes the camera's own entity.
//? if >=26.1 {
/*@Shadow
public abstract Entity entity();
@Inject(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
public void lockRotation(float tickDelta, CallbackInfo ci) {
saturn$lockRotation(entity());
}
// The camera works out the FOV from 26.1 (GameRenderer.getFov before).
@Inject(method = "calculateFov", at = @At("RETURN"), cancellable = true)
private void onCalculateFov(float tickDelta, CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(ZoomFov.apply(cir.getReturnValue()));
}
*///?} else if >=1.21.11 {
@Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
public void lockRotation(
//? if >=1.21.11 {
Level focusedBlock,
//?} else
//BlockGetter focusedBlock,
Entity cameraEntity,
boolean isThirdPerson,
boolean isFrontFacing,
float tickDelta,
CallbackInfo ci) {
public void lockRotation(Level focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing,
float tickDelta, CallbackInfo ci) {
saturn$lockRotation(cameraEntity);
}
//?} else {
/*@Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
public void lockRotation(BlockGetter focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing,
float tickDelta, CallbackInfo ci) {
saturn$lockRotation(cameraEntity);
}
*///?}
@Unique
private void saturn$lockRotation(Entity cameraEntity) {
if (FreelookMod.isFreeLooking() && cameraEntity instanceof LocalPlayer) {
CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity;
@@ -8,8 +8,7 @@ import net.minecraft.client.renderer.GameRenderer;
//? if >=1.21.6
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.saturnclient.mod.mods.FreelookMod;
import org.saturnclient.mod.mods.ZoomMod;
import org.saturnclient.impl.features.render.ZoomFov;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -18,11 +17,11 @@ import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Overrides the game's field-of-view when {@link ZoomMod} is active.
* Overrides the game's field-of-view when {@link org.saturnclient.mod.mods.ZoomMod} is active.
*
* The old code checked both {@code Zoom.isZooming} and
* {@code Zoom.shouldZoom()} separately. The refactored
* {@link ZoomMod#shouldZoom()} already ORs both conditions
* {@link org.saturnclient.mod.mods.ZoomMod#shouldZoom()} already ORs both conditions
* ({@code enabled.value && isZooming}), so only one check is needed.
*
* No {@link org.saturnclient.common.module.ModProvider} is required
@@ -36,20 +35,21 @@ public class GameRendererMixin {
* After the vanilla FOV has been computed, divide it by the zoom
* level to produce the narrowed field-of-view.
*/
// From 26.1 the camera works out the FOV, so CameraMixin applies the zoom there.
//? if <26.1 {
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
private void onGetFov(CallbackInfoReturnable<Float> cir) {
if (ZoomMod.shouldZoom()) {
float zoomedFov = cir.getReturnValue() / ZoomMod.getZoomLevel();
cir.setReturnValue(zoomedFov);
} else if (FreelookMod.shouldZoomOut()) {
float zoomedFov = cir.getReturnValue() / FreelookMod.getZoomLevel();
cir.setReturnValue(zoomedFov);
}
cir.setReturnValue(ZoomFov.apply(cir.getReturnValue()));
}
//?}
//? if >=1.21.6 {
// From 1.21.6 the menu blur's strength is the vanilla setting, read here every frame. While a
// Saturn screen is open, it's Saturn's background blur instead, faded in with the screen.
// From 26.1 the setting is read while extracting the options' render state.
//? if >=26.1
//@Redirect(method = "extractOptions", at = @At(value = "INVOKE",
//? if <26.1
@Redirect(method = "render", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/Options;getMenuBackgroundBlurriness()I"))
private int useSaturnBlur(Options options) {
@@ -3,6 +3,9 @@ package org.saturnclient.impl.features.mixins.render;
// Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
//? if >=26.1 {
/*import net.minecraft.client.renderer.LightmapRenderStateExtractor;
*///?} else
import net.minecraft.client.renderer.LightTexture;
import org.saturnclient.mod.mods.FullbrightMod;
@@ -18,6 +21,10 @@ import org.spongepowered.asm.mixin.injection.At;
* it is now {@link FullbrightMod}. The injection target and
* strategy are unchanged.
*/
// From 26.1 the gamma is worked out while extracting the lightmap's render state.
//? if >=26.1 {
/*@Mixin(LightmapRenderStateExtractor.class)
*///?} else
@Mixin(LightTexture.class)
public class LightmapTextureManagerMixin {
@@ -25,6 +32,9 @@ public class LightmapTextureManagerMixin {
* If fullbright is active, substitute the feature's brightness
* value for whatever vanilla calculated.
*/
//? if >=26.1 {
/*@ModifyExpressionValue(method = "extract", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
*///?} else
@ModifyExpressionValue(method = "updateLightTexture", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
private float modifyGamma(float original) {
if (FullbrightMod.shouldOverrideBrightness()) {
@@ -40,6 +40,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Gui.class)
public class RenderMixin {
//? if >=26.1 {
/*@Inject(method = "extractChat", at = @At("TAIL"))
*///?} else
@Inject(method = "renderChat", at = @At("TAIL"))
public void renderMods(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
Font textRenderer = SaturnClient.client.font;
@@ -0,0 +1,19 @@
package org.saturnclient.impl.features.render;
import org.saturnclient.mod.mods.FreelookMod;
import org.saturnclient.mod.mods.ZoomMod;
/** The zoom mods' change to the field of view, applied where each version computes it. */
public final class ZoomFov {
private ZoomFov() {
}
public static float apply(float fov) {
if (ZoomMod.shouldZoom()) {
return fov / ZoomMod.getZoomLevel();
} else if (FreelookMod.shouldZoomOut()) {
return fov / FreelookMod.getZoomLevel();
}
return fov;
}
}
@@ -92,6 +92,9 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef {
break;
case Options:
//? if >=26.1 {
/*this.setScreen(new OptionsScreen(exitTarget, this.options, ((Minecraft) (Object) this).level != null));
*///?} else
this.setScreen(new OptionsScreen(exitTarget, this.options));
break;
}
@@ -37,6 +37,10 @@ import net.minecraft.client.renderer.PostChain;
import net.minecraft.client.renderer.RenderPipelines;
import com.mojang.blaze3d.pipeline.TextureTarget;
import net.minecraft.client.renderer.LevelTargetBundle;
//? if >=26.1 {
/*import net.minecraft.client.renderer.Projection;
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
*///?} else
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.gui.render.TextureSetup;
@@ -54,6 +58,15 @@ public final class GlassRenderer {
.build();
private static final Identifier BLUR = Identifier.withDefaultNamespace("post_effect/blur.json");
//? if >=26.1 {
/*private static final Projection PROJECTION = new Projection();
private static final ProjectionMatrixBuffer PROJECTION_BUFFER = new ProjectionMatrixBuffer("saturn glass");
static {
// As vanilla sets up its post chains' projection; the chain sizes it when it runs.
PROJECTION.setupOrtho(0.1f, 1000f, 1f, 1f, false);
}
*///?} else
private static final CachedOrthoProjectionMatrixBuffer PROJECTION = new CachedOrthoProjectionMatrixBuffer("saturn glass", 0.1f, 1000f, false);
// For the blurs' intermediate targets, freed once unused for a few frames
private static final CrossFrameResourcePool POOL = new CrossFrameResourcePool(3);
@@ -140,6 +153,9 @@ public final class GlassRenderer {
PostChainConfig pipeline = PostChainConfig.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
return PostChain.load(pipeline, SaturnClient.client.getTextureManager(),
//? if >=26.1 {
/*LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION, PROJECTION_BUFFER);
*///?} else
LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION);
} catch (Exception e) {
SaturnClient.LOGGER.error("Couldn't load the glass blur", e);
@@ -325,6 +325,9 @@ public class RenderScopeImpl implements RenderScope {
entity, seed);
try {
//? if >=26.1 {
/*this.state.submitItem(new GuiItemRenderState(
*///?} else
this.state.submitItem(new GuiItemRenderState(stack.getItem().getName().toString(),
new Matrix3x2f(this.matrices.stack), keyedItemRenderState, x, y,
this.scissorStack.peekLast()));
@@ -13,6 +13,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.renderer.CubeMap;
import net.minecraft.client.gui.GuiGraphics;
//? if >=26.1 {
/*import net.minecraft.client.renderer.Panorama;
*///?} else
import net.minecraft.client.renderer.PanoramaRenderer;
import net.minecraft.client.gui.screens.Screen;
//? if >=1.21.6 {
@@ -34,13 +37,18 @@ import net.minecraft.resources.Identifier;
public class SaturnScreenFabric extends Screen implements ScreenProvider {
protected static final Identifier PANORAMA = Identifier.fromNamespaceAndPath("saturnclient",
"textures/gui/title/background/panorama");
protected static final CubeMap PANORAMA_RENDERER = new CubeMap(PANORAMA);
public static final CubeMap PANORAMA_RENDERER = new CubeMap(PANORAMA);
// From 26.1 vanilla draws the panorama from its own cube map; GuiRendererMixin swaps in Saturn's.
//? if >=26.1 {
/*public static final Panorama ROTATING_PANORAMA_RENDERER = new Panorama();
*///?} else {
public static final PanoramaRenderer ROTATING_PANORAMA_RENDERER;
static {
ROTATING_PANORAMA_RENDERER = new PanoramaRenderer(PANORAMA_RENDERER);
}
//?}
//? if <1.21.6
//private final CrossFrameResourcePool pool = new CrossFrameResourcePool(3);
@@ -81,6 +89,9 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
//? if <1.21.6
//@SuppressWarnings("deprecation")
@Override
//? if >=26.1 {
/*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) {
*///?} else
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
if (screen.start == null) {
screen.start = Instant.now();
@@ -128,10 +139,16 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
// Vanilla's background would cover Saturn's panorama, and blurs by the vanilla setting. The
// strength of this blur comes from GameRendererMixin, which gives Saturn's setting instead.
@Override
//? if >=26.1 {
/*public void extractBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
*///?} else
public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
// The panorama renderer has no opacity (and takes no delta) from 1.21.6, so it's drawn fully
// opaque.
if (isShowingPanorama()) {
//? if >=26.1 {
/*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height, true);
*///?} else
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true);
}
@@ -140,6 +157,9 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
}
if (minecraft.level != null) {
//? if >=26.1 {
/*extractMenuBackground(context);
*///?} else
renderMenuBackground(context);
}
}
@@ -1,6 +1,9 @@
package org.saturnclient.saturnclient.event;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
//? if >=26.1 {
/*import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
*///?} else
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.KeyMapping;
import com.mojang.blaze3d.platform.InputConstants;
@@ -32,7 +35,13 @@ public class KeyInputHandler {
private static KeyMapping mainMenuKeyBinding;
public static void register() {
//? if >=1.21.9 {
//? if >=26.1 {
/*mainMenuKeyBinding = KeyMappingHelper.registerKeyMapping(
new KeyMapping(
MAIN_MENU_KEY_ID,
DEFAULT_MENU_KEY,
KeyMapping.Category.register(KEY_CATEGORY)));
*///?} else if >=1.21.9 {
mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(
new KeyMapping(
MAIN_MENU_KEY_ID,
@@ -0,0 +1,35 @@
package org.saturnclient.saturnclient.mixin;
//? if >=26.1 {
/*import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.render.GuiRenderer;
import net.minecraft.client.renderer.CubeMap;
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.spongepowered.asm.mixin.injection.At;
*///?} else
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
/**
* From 26.1 GuiRenderer draws the menu panorama from its own cube map, which is vanilla's. While a
* Saturn screen is open, this draws Saturn's panorama instead. Before 26.1 SaturnScreenFabric draws
* the panorama itself, so this mixin is empty there, and it targets Minecraft because GuiRenderer
* doesn't exist before 1.21.6. It stays registered, keeping saturnclient.mixins.json the same for
* every version.
*/
//? if >=26.1 {
/*@Mixin(GuiRenderer.class)
public class GuiRendererMixin {
@WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/CubeMap;render(FF)V"))
private void saturn$drawSaturnPanorama(CubeMap cubeMap, float xRot, float yRot, Operation<Void> original) {
boolean saturnScreen = Minecraft.getInstance().screen instanceof SaturnScreenFabric;
original.call(saturnScreen ? SaturnScreenFabric.PANORAMA_RENDERER : cubeMap, xRot, yRot);
}
}
*///?} else {
@Mixin(Minecraft.class)
public class GuiRendererMixin {
}
//?}
@@ -54,6 +54,9 @@ public abstract class SplashOverlayMixin {
* @reason Replace vanilla splash rendering completely but keep exit logic
*/
@Overwrite
//? if >=26.1 {
/*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) {
*///?} else
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
int screenWidth = context.guiWidth();
int screenHeight = context.guiHeight();
@@ -116,6 +119,9 @@ public abstract class SplashOverlayMixin {
private void renderCurrentScreen(GuiGraphics context, int mouseX, int mouseY, float delta) {
if (minecraft.screen != null) {
//? if >=26.1 {
/*minecraft.screen.extractRenderState(context, mouseX, mouseY, delta);
*///?} else
minecraft.screen.render(context, mouseX, mouseY, delta);
}
}
@@ -129,7 +135,9 @@ public abstract class SplashOverlayMixin {
private void renderProgressBar(RenderScope scope, GuiGraphics context, int screenWidth, int screenHeight,
int alpha) {
int progressY = ((screenHeight - 5) / 2) + 25;
//? if >=1.21.11 {
//? if >=26.1 {
/*context.outline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
*///?} else if >=1.21.11 {
context.renderOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
//?} else if >=1.21.9 {
/*context.submitOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));