feat: 1.21 port

This commit is contained in:
2025-09-01 02:06:58 +08:00
parent 62eee93fea
commit 7a0e44b88c
16 changed files with 92 additions and 112 deletions

View File

@@ -1,7 +1,7 @@
plugins { plugins {
id 'dev.architectury.loom' version '1.11-SNAPSHOT' apply false id 'dev.architectury.loom' version '1.11-SNAPSHOT' apply false
id 'architectury-plugin' version '3.4-SNAPSHOT' id 'architectury-plugin' version '3.4-SNAPSHOT'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false id 'com.gradleup.shadow' version '8.3.6' apply false
} }
architectury { architectury {
@@ -31,6 +31,10 @@ subprojects {
// for more information about repositories. // for more information about repositories.
} }
loom {
silentMojangMappingsLicense()
}
dependencies { dependencies {
minecraft "net.minecraft:minecraft:$rootProject.minecraft_version" minecraft "net.minecraft:minecraft:$rootProject.minecraft_version"
mappings loom.officialMojangMappings() mappings loom.officialMojangMappings()
@@ -42,12 +46,12 @@ subprojects {
// If you remove this line, sources will not be generated. // If you remove this line, sources will not be generated.
withSourcesJar() withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_21
} }
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
it.options.release = 17 it.options.release = 21
} }
// Configure Maven publishing. // Configure Maven publishing.
@@ -65,6 +69,7 @@ subprojects {
// Notice: This block does NOT have the same function as the block in the top level. // Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for // The repositories here will be used for publishing your artifact, not for
// retrieving dependencies. // retrieving dependencies.
mavenLocal()
} }
} }
} }

View File

@@ -7,5 +7,5 @@ dependencies {
// which get remapped to the correct annotations on each platform. // which get remapped to the correct annotations on each platform.
// Do NOT use other classes from Fabric Loader. // Do NOT use other classes from Fabric Loader.
modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version" modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version"
compileOnly("com.mojang:authlib:4.0.43") compileOnly("com.mojang:authlib:6.0.54")
} }

View File

@@ -3,16 +3,13 @@ package net.magicterra.skinfix.inject;
import com.mojang.authlib.Environment; import com.mojang.authlib.Environment;
import com.mojang.authlib.EnvironmentParser; import com.mojang.authlib.EnvironmentParser;
import com.mojang.authlib.GameProfileRepository; import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.HttpAuthenticationService;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.minecraft.UserApiService; import com.mojang.authlib.minecraft.client.MinecraftClient;
import com.mojang.authlib.yggdrasil.ServicesKeySet; import com.mojang.authlib.yggdrasil.ServicesKeySet;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; import com.mojang.authlib.yggdrasil.YggdrasilEnvironment;
import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository;
import com.mojang.authlib.yggdrasil.YggdrasilServicesKeyInfo; import com.mojang.authlib.yggdrasil.YggdrasilServicesKeyInfo;
import com.mojang.authlib.yggdrasil.YggdrasilUserApiService;
import java.net.Proxy; import java.net.Proxy;
import java.net.URL; import java.net.URL;
@@ -37,13 +34,14 @@ public class InjectYggdrasilAuthenticationService extends YggdrasilAuthenticatio
super(proxy, environment); super(proxy, environment);
this.environment = environment; this.environment = environment;
final URL publicKeySetUrl = HttpAuthenticationService.constantURL("https://mc.gardel.top/minecraftservices/publickeys"); final MinecraftClient client = MinecraftClient.unauthenticated(proxy);
this.servicesKeySet = YggdrasilServicesKeyInfo.get(publicKeySetUrl, this); final URL publicKeySetUrl = constantURL("https://mc.gardel.top/minecraftservices/publickeys");
this.servicesKeySet = YggdrasilServicesKeyInfo.get(publicKeySetUrl, client);
} }
@Override @Override
public MinecraftSessionService createMinecraftSessionService() { public MinecraftSessionService createMinecraftSessionService() {
return new InjectYggdrasilMinecraftSessionService(this, environment); return new InjectYggdrasilMinecraftSessionService(servicesKeySet, getProxy(), environment);
} }
@Override @Override
@@ -53,21 +51,6 @@ public class InjectYggdrasilAuthenticationService extends YggdrasilAuthenticatio
@Override @Override
public GameProfileRepository createProfileRepository() { public GameProfileRepository createProfileRepository() {
return new YggdrasilGameProfileRepository(this, Environment.create( return new YggdrasilGameProfileRepository(getProxy(), new Environment(environment.sessionHost(), "https://mc.gardel.top/minecraftservices", environment.name()));
environment.getAuthHost(),
"https://mc.gardel.top/api",
environment.getSessionHost(),
environment.getServicesHost(),
environment.getName()));
}
@Override
public UserApiService createUserApiService(String accessToken) throws AuthenticationException {
return new YggdrasilUserApiService(accessToken, getProxy(), Environment.create(
environment.getAuthHost(),
environment.getAccountsHost(),
environment.getSessionHost(),
"https://mc.gardel.top/minecraftservices",
environment.getName()));
} }
} }

View File

@@ -1,22 +1,20 @@
package net.magicterra.skinfix.inject; package net.magicterra.skinfix.inject;
import com.google.common.collect.Iterables;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.mojang.authlib.Environment; import com.mojang.authlib.Environment;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.SignatureState;
import com.mojang.authlib.minecraft.InsecurePublicKeyException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTextures;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
import com.mojang.authlib.yggdrasil.ServicesKeySet; import com.mojang.authlib.yggdrasil.ServicesKeySet;
import com.mojang.authlib.yggdrasil.ServicesKeyType; import com.mojang.authlib.yggdrasil.ServicesKeyType;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.Proxy;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -28,12 +26,15 @@ import org.slf4j.LoggerFactory;
* @since 2025-04-26 22:11 * @since 2025-04-26 22:11
*/ */
public class InjectYggdrasilMinecraftSessionService extends YggdrasilMinecraftSessionService { public class InjectYggdrasilMinecraftSessionService extends YggdrasilMinecraftSessionService {
private static final Logger LOGGER = LoggerFactory.getLogger(InjectYggdrasilMinecraftSessionService.class); private static final Logger LOGGER = LoggerFactory.getLogger(YggdrasilMinecraftSessionService.class);
private final Gson gson; private final Gson gson;
protected InjectYggdrasilMinecraftSessionService(YggdrasilAuthenticationService service, Environment env) { private final ServicesKeySet servicesKeySet;
super(service, env);
protected InjectYggdrasilMinecraftSessionService(ServicesKeySet servicesKeySet, Proxy proxy, Environment env) {
super(servicesKeySet, proxy, env);
this.servicesKeySet = servicesKeySet;
try { try {
Field field = YggdrasilMinecraftSessionService.class.getDeclaredField("gson"); Field field = YggdrasilMinecraftSessionService.class.getDeclaredField("gson");
field.setAccessible(true); field.setAccessible(true);
@@ -44,52 +45,47 @@ public class InjectYggdrasilMinecraftSessionService extends YggdrasilMinecraftSe
} }
@Override @Override
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) throws InsecurePublicKeyException { public MinecraftProfileTextures unpackTextures(Property packedTextures) {
final Property textureProperty = Iterables.getFirst(profile.getProperties().get("textures"), null); final String value = packedTextures.value();
final SignatureState signatureState = getPropertySignatureState(packedTextures);
if (textureProperty == null) {
return new HashMap<>();
}
final String value = requireSecure ? getSecurePropertyValue(textureProperty) : textureProperty.getValue();
final MinecraftTexturesPayload result; final MinecraftTexturesPayload result;
try { try {
final String json = new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); final String json = new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
result = gson.fromJson(json, MinecraftTexturesPayload.class); result = gson.fromJson(json, MinecraftTexturesPayload.class);
} catch (final JsonParseException e) { } catch (final JsonParseException | IllegalArgumentException e) {
LOGGER.error("Could not decode textures payload", e); LOGGER.error("Could not decode textures payload", e);
return new HashMap<>(); return MinecraftProfileTextures.EMPTY;
} }
if (result == null || result.getTextures() == null) { if (result == null || result.textures() == null || result.textures().isEmpty()) {
return new HashMap<>(); return MinecraftProfileTextures.EMPTY;
} }
for (final Map.Entry<MinecraftProfileTexture.Type, MinecraftProfileTexture> entry : result.getTextures().entrySet()) { final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = result.textures();
for (final Map.Entry<MinecraftProfileTexture.Type, MinecraftProfileTexture> entry : textures.entrySet()) {
final String url = entry.getValue().getUrl(); final String url = entry.getValue().getUrl();
if (!InjectTextureUrlChecker.isAllowedTextureDomain(url)) { if (!InjectTextureUrlChecker.isAllowedTextureDomain(url)) {
LOGGER.error("Textures payload contains blocked domain: {}", url); LOGGER.error("Textures payload contains blocked domain: {}", url);
return new HashMap<>(); return MinecraftProfileTextures.EMPTY;
} }
} }
return result.getTextures(); return new MinecraftProfileTextures(
textures.get(MinecraftProfileTexture.Type.SKIN),
textures.get(MinecraftProfileTexture.Type.CAPE),
textures.get(MinecraftProfileTexture.Type.ELYTRA),
signatureState
);
} }
@Override private SignatureState getPropertySignatureState(final Property property) {
public String getSecurePropertyValue(final Property property) throws InsecurePublicKeyException {
if (!property.hasSignature()) { if (!property.hasSignature()) {
LOGGER.error("Signature is missing from Property {}", property.getName()); return SignatureState.UNSIGNED;
throw new InsecurePublicKeyException.MissingException();
} }
final ServicesKeySet servicesKeySet = getAuthenticationService().getServicesKeySet();
if (servicesKeySet.keys(ServicesKeyType.PROFILE_PROPERTY).stream().noneMatch(key -> key.validateProperty(property))) { if (servicesKeySet.keys(ServicesKeyType.PROFILE_PROPERTY).stream().noneMatch(key -> key.validateProperty(property))) {
LOGGER.error("Property {} has been tampered with (signature invalid)", property.getName()); return SignatureState.INVALID;
throw new InsecurePublicKeyException.InvalidException("Property has been tampered with (signature invalid)");
} }
return SignatureState.SIGNED;
return property.getValue();
} }
} }

View File

@@ -1,14 +1,13 @@
{ {
"required": true, "required": true,
"package": "net.magicterra.skinfix.mixin", "package": "net.magicterra.skinfix.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_21",
"minVersion": "0.8", "minVersion": "0.8",
"client": [ "client": [
"MinecraftMixin" "MinecraftMixin",
],
"mixins": [
"ProfilePublicKeyDataMixin" "ProfilePublicKeyDataMixin"
], ],
"mixins": [],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
} }

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id 'com.github.johnrengelman.shadow' id 'com.gradleup.shadow'
} }
architectury { architectury {
@@ -46,5 +46,5 @@ shadowJar {
} }
remapJar { remapJar {
input.set shadowJar.archiveFile inputFile.set shadowJar.archiveFile
} }

View File

@@ -25,9 +25,9 @@
"skinfix.mixins.json" "skinfix.mixins.json"
], ],
"depends": { "depends": {
"fabricloader": ">=0.17.2", "fabricloader": ">=0.17",
"minecraft": "~1.20.1", "minecraft": ">=1.21",
"java": ">=17" "java": ">=21"
}, },
"suggests": { "suggests": {
"another-mod": "*" "another-mod": "*"

View File

@@ -1 +0,0 @@
loom.platform=forge

View File

@@ -1,12 +0,0 @@
package net.magicterra.skinfix.forge;
import net.magicterra.skinfix.SkinFixMod;
import net.minecraftforge.fml.common.Mod;
@Mod(SkinFixMod.MOD_ID)
public final class SkinFixModForge {
public SkinFixModForge() {
// Run our common setup.
SkinFixMod.init();
}
}

View File

@@ -1,6 +0,0 @@
{
"pack": {
"description": "skinfix resources",
"pack_format": 15
}
}

View File

@@ -5,9 +5,9 @@ org.gradle.parallel=true
mod_version=1.0-SNAPSHOT mod_version=1.0-SNAPSHOT
maven_group=net.magicterra maven_group=net.magicterra
archives_name=skinfix archives_name=skinfix
enabled_platforms=fabric,forge enabled_platforms=fabric,neoforge
# Minecraft properties # Minecraft properties
minecraft_version=1.20.1 minecraft_version=1.21
# Dependencies # Dependencies
fabric_loader_version=0.17.2 fabric_loader_version=0.17.2
forge_version=1.20.1-47.4.6 neoforge_version=21.0.167

View File

@@ -1,16 +1,10 @@
plugins { plugins {
id 'com.github.johnrengelman.shadow' id 'com.gradleup.shadow'
}
loom {
forge {
mixinConfig "skinfix.mixins.json"
}
} }
architectury { architectury {
platformSetupLoomIde() platformSetupLoomIde()
forge() neoForge()
} }
configurations { configurations {
@@ -20,7 +14,7 @@ configurations {
} }
compileClasspath.extendsFrom common compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common runtimeClasspath.extendsFrom common
developmentForge.extendsFrom common developmentNeoForge.extendsFrom common
// Files in this configuration will be bundled into your mod using the Shadow plugin. // Files in this configuration will be bundled into your mod using the Shadow plugin.
// Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
@@ -30,18 +24,25 @@ configurations {
} }
} }
repositories {
maven {
name = 'NeoForged'
url = 'https://maven.neoforged.net/releases'
}
}
dependencies { dependencies {
forge "net.minecraftforge:forge:$rootProject.forge_version" neoForge "net.neoforged:neoforge:$rootProject.neoforge_version"
common(project(path: ':common', configuration: 'namedElements')) { transitive false } common(project(path: ':common', configuration: 'namedElements')) { transitive false }
shadowBundle project(path: ':common', configuration: 'transformProductionForge') shadowBundle project(path: ':common', configuration: 'transformProductionNeoForge')
} }
processResources { processResources {
inputs.property 'version', project.version inputs.property 'version', project.version
filesMatching('META-INF/mods.toml') { filesMatching('META-INF/neoforge.mods.toml') {
expand version: project.version expand version: project.version
} }
} }
@@ -52,5 +53,5 @@ shadowJar {
} }
remapJar { remapJar {
input.set shadowJar.archiveFile inputFile.set shadowJar.archiveFile
} }

View File

@@ -0,0 +1 @@
loom.platform=neoforge

View File

@@ -0,0 +1,12 @@
package net.magicterra.skinfix.neoforge;
import net.magicterra.skinfix.SkinFixMod;
import net.neoforged.fml.common.Mod;
@Mod(SkinFixMod.MOD_ID)
public final class SkinFixModNeoForge {
public SkinFixModNeoForge() {
// Run our common setup.
SkinFixMod.init();
}
}

View File

@@ -1,5 +1,5 @@
modLoader = "javafml" modLoader = "javafml"
loaderVersion = "[47,)" loaderVersion = "[1,)"
#issueTrackerURL = "" #issueTrackerURL = ""
license = "MIT" license = "MIT"
@@ -14,16 +14,18 @@ Fix Minecraft does not load skin png not prefix with minecraft.net
logoFile="assets/skinfix/icon.png" logoFile="assets/skinfix/icon.png"
[[dependencies.skinfix]] [[dependencies.skinfix]]
modId = "forge" modId = "neoforge"
mandatory = true type="required"
versionRange = "[47,)" versionRange = "[21.0.0-beta,)"
ordering = "NONE" ordering = "NONE"
side = "BOTH" side = "BOTH"
[[dependencies.skinfix]] [[dependencies.skinfix]]
modId = "minecraft" modId = "minecraft"
mandatory = true type="required"
versionRange = "[1.20.1,)" versionRange = "[1.21,1.21.5)"
ordering = "NONE" ordering = "NONE"
side = "BOTH" side = "BOTH"
[[mixins]]
config="skinfix.mixins.json"

View File

@@ -11,4 +11,4 @@ rootProject.name = 'skinfix'
include 'common' include 'common'
include 'fabric' include 'fabric'
include 'forge' include 'neoforge'