From 11ecf88eb2c7ae4f13c32c1261491efa5678188b Mon Sep 17 00:00:00 2001 From: Lionarius Date: Thu, 24 Jul 2025 03:20:16 +0300 Subject: [PATCH 1/8] add OnServerStopped event --- .../net/lionarius/skinrestorer/SkinRestorer.java | 14 ++++++++++++++ .../fabric/mixin/MinecraftServerMixin.java | 6 ++++++ .../skinrestorer/forge/SkinRestorerForge.java | 6 ++++++ .../neoforge/SkinRestorerNeoForge.java | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/common/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java b/common/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java index d8ac710..f2270f1 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java +++ b/common/src/main/java/net/lionarius/skinrestorer/SkinRestorer.java @@ -19,6 +19,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +38,7 @@ public final class SkinRestorer { private static Path configDir; private static Config config; private static TickedScheduler tickedScheduler; + private static MinecraftServer minecraftServer; private SkinRestorer() {} @@ -60,6 +62,10 @@ public final class SkinRestorer { return SkinRestorer.tickedScheduler; } + public static @Nullable MinecraftServer getMinecraftServer() { + return SkinRestorer.minecraftServer; + } + public static Optional getProvider(String name) { return Optional.ofNullable(SkinRestorer.providersRegistry.get(name)); } @@ -173,6 +179,14 @@ public final class SkinRestorer { SkinRestorer.skinStorage = new SkinStorage(new SkinIO(worldSkinDirectory)); SkinRestorer.tickedScheduler = new TickedScheduler(server); server.addTickable(SkinRestorer.tickedScheduler); + + SkinRestorer.minecraftServer = server; + } + + public static void onServerStopped(MinecraftServer server) { + SkinRestorer.skinStorage = null; + SkinRestorer.tickedScheduler = null; + SkinRestorer.minecraftServer = null; } public static void onCommandRegister(CommandDispatcher dispatcher) { diff --git a/fabric/src/main/java/net/lionarius/skinrestorer/fabric/mixin/MinecraftServerMixin.java b/fabric/src/main/java/net/lionarius/skinrestorer/fabric/mixin/MinecraftServerMixin.java index f14d1ce..6212f81 100644 --- a/fabric/src/main/java/net/lionarius/skinrestorer/fabric/mixin/MinecraftServerMixin.java +++ b/fabric/src/main/java/net/lionarius/skinrestorer/fabric/mixin/MinecraftServerMixin.java @@ -15,4 +15,10 @@ public abstract class MinecraftServerMixin { private void onServerStarted(CallbackInfo ci) { SkinRestorer.Events.onServerStarted((MinecraftServer) (Object) this); } + + @Inject(method = "runServer", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;onServerExit()V")) + private void onServerStopped(CallbackInfo ci) { + SkinRestorer.Events.onServerStopped((MinecraftServer) (Object) this); + } } diff --git a/forge/src/main/java/net/lionarius/skinrestorer/forge/SkinRestorerForge.java b/forge/src/main/java/net/lionarius/skinrestorer/forge/SkinRestorerForge.java index da046fb..fb9d0fd 100644 --- a/forge/src/main/java/net/lionarius/skinrestorer/forge/SkinRestorerForge.java +++ b/forge/src/main/java/net/lionarius/skinrestorer/forge/SkinRestorerForge.java @@ -4,6 +4,7 @@ import net.lionarius.skinrestorer.SkinRestorer; import net.lionarius.skinrestorer.compat.skinshuffle.SkinShuffleCompatibility; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -27,4 +28,9 @@ public final class SkinRestorerForge { public static void onServerStarted(ServerStartedEvent event) { SkinRestorer.Events.onServerStarted(event.getServer()); } + + @SubscribeEvent + public static void onServerStopped(ServerStoppedEvent event) { + SkinRestorer.Events.onServerStopped(event.getServer()); + } } diff --git a/neoforge/src/main/java/net/lionarius/skinrestorer/neoforge/SkinRestorerNeoForge.java b/neoforge/src/main/java/net/lionarius/skinrestorer/neoforge/SkinRestorerNeoForge.java index b18b23c..e03e127 100644 --- a/neoforge/src/main/java/net/lionarius/skinrestorer/neoforge/SkinRestorerNeoForge.java +++ b/neoforge/src/main/java/net/lionarius/skinrestorer/neoforge/SkinRestorerNeoForge.java @@ -7,6 +7,7 @@ import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppedEvent; @Mod(SkinRestorer.MOD_ID) @EventBusSubscriber(modid = SkinRestorer.MOD_ID) @@ -28,4 +29,9 @@ public final class SkinRestorerNeoForge { public static void onServerStarted(ServerStartedEvent event) { SkinRestorer.Events.onServerStarted(event.getServer()); } + + @SubscribeEvent + public static void onServerStopped(ServerStoppedEvent event) { + SkinRestorer.Events.onServerStopped(event.getServer()); + } } From 84a06898a2a134633e449e10a18770c323984aec Mon Sep 17 00:00:00 2001 From: Lionarius Date: Thu, 24 Jul 2025 03:21:43 +0300 Subject: [PATCH 2/8] do not replace skin for head if on client and not in singleplayer --- .../lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java index 5415368..248a765 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java +++ b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java @@ -43,6 +43,9 @@ public abstract class SkullBlockEntityMixin { @Unique private static void skinrestorer$replaceSkin(Optional profileOpt, CallbackInfoReturnable>> cir) { + if (SkinRestorer.getMinecraftServer() == null) + return; + if (profileOpt.isEmpty()) return; From 8d4c2e9f83abc5c45a26253d01220b98595f3c85 Mon Sep 17 00:00:00 2001 From: Lionarius Date: Thu, 24 Jul 2025 03:22:56 +0300 Subject: [PATCH 3/8] add 1.21.8 to supported versions --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c23b610..8d34a53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ java_version=21 # Common minecraft_version=1.21.6 -minecraft_version_list=1.21.6,1.21.7 +minecraft_version_list=1.21.6,1.21.7,1.21.8 minecraft_version_range=[1.21.6, 1.22) mod_id=skinrestorer mod_name=SkinRestorer From f604ac2c8bbcf2f7fdd29fcff5f033dd6eb16e7b Mon Sep 17 00:00:00 2001 From: Lionarius Date: Thu, 24 Jul 2025 03:33:05 +0300 Subject: [PATCH 4/8] add support for additional modloaders when publishing --- buildSrc/src/main/groovy/multiloader-publish.gradle | 8 ++++++++ fabric/gradle.properties | 1 + 2 files changed, 9 insertions(+) diff --git a/buildSrc/src/main/groovy/multiloader-publish.gradle b/buildSrc/src/main/groovy/multiloader-publish.gradle index affceee..9817b23 100644 --- a/buildSrc/src/main/groovy/multiloader-publish.gradle +++ b/buildSrc/src/main/groovy/multiloader-publish.gradle @@ -6,6 +6,14 @@ publishMods { file = project.layout.buildDirectory.file("libs/${project.archivesBaseName}-${project.version}.jar").map { it.asFile }.getOrNull() modLoaders.add(project.name) + + if (project.hasProperty('additional_modloaders') && !additional_modloaders.isEmpty()) + { + def loaders = additional_modloaders.split(',') + for (loader in loaders) + modLoaders.add(loader) + } + type = STABLE version = project.version displayName = "[${project.name.capitalize()}] ${mod_name} ${mod_version}" diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 9ac3893..d4f3a28 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -3,3 +3,4 @@ fabric_loader_version=0.15.0 fabric_api_version=0.127.0+1.21.6 optional_dependencies=fabric-api +additional_modloaders=quilt From 1d8472e35c3d2a1f12dfe3118688570bf9efbfe2 Mon Sep 17 00:00:00 2001 From: Lionarius Date: Sat, 26 Jul 2025 10:12:27 +0300 Subject: [PATCH 5/8] fix server freeze when loading head --- .../skinrestorer/mixin/ChunkMapAccessor.java | 2 +- .../mixin/GameProfileCacheAccessor.java | 14 ++++++++++++++ .../skinrestorer/mixin/SkullBlockEntityMixin.java | 10 ++++++++-- .../lionarius/skinrestorer/util/PlayerUtils.java | 4 ++-- .../main/resources/META-INF/accesstransformer.cfg | 1 + .../src/main/resources/skinrestorer.accesswidener | 3 +++ common/src/main/resources/skinrestorer.mixins.json | 3 ++- fabric/src/main/resources/fabric.mod.json | 1 + forge/build.gradle | 6 +++++- neoforge/build.gradle | 4 ++-- 10 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 common/src/main/java/net/lionarius/skinrestorer/mixin/GameProfileCacheAccessor.java create mode 100644 common/src/main/resources/META-INF/accesstransformer.cfg create mode 100644 common/src/main/resources/skinrestorer.accesswidener diff --git a/common/src/main/java/net/lionarius/skinrestorer/mixin/ChunkMapAccessor.java b/common/src/main/java/net/lionarius/skinrestorer/mixin/ChunkMapAccessor.java index 2d36edb..128d737 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/mixin/ChunkMapAccessor.java +++ b/common/src/main/java/net/lionarius/skinrestorer/mixin/ChunkMapAccessor.java @@ -9,5 +9,5 @@ import org.spongepowered.asm.mixin.gen.Accessor; public interface ChunkMapAccessor { @Accessor - Int2ObjectMap getEntityMap(); + Int2ObjectMap getEntityMap(); } diff --git a/common/src/main/java/net/lionarius/skinrestorer/mixin/GameProfileCacheAccessor.java b/common/src/main/java/net/lionarius/skinrestorer/mixin/GameProfileCacheAccessor.java new file mode 100644 index 0000000..f129fc3 --- /dev/null +++ b/common/src/main/java/net/lionarius/skinrestorer/mixin/GameProfileCacheAccessor.java @@ -0,0 +1,14 @@ +package net.lionarius.skinrestorer.mixin; + +import net.minecraft.server.players.GameProfileCache; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(GameProfileCache.class) +public interface GameProfileCacheAccessor { + + @Accessor + Map getProfilesByName(); +} diff --git a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java index 248a765..21d4d38 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java +++ b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java @@ -3,6 +3,7 @@ package net.lionarius.skinrestorer.mixin; import com.mojang.authlib.GameProfile; import net.lionarius.skinrestorer.SkinRestorer; import net.lionarius.skinrestorer.util.PlayerUtils; +import net.minecraft.Util; import net.minecraft.server.Services; import net.minecraft.world.level.block.entity.SkullBlockEntity; import org.spongepowered.asm.mixin.Mixin; @@ -11,6 +12,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.Locale; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -25,7 +27,11 @@ public abstract class SkullBlockEntityMixin { if (name == null) return; - var profileOpt = services.profileCache().get(name); + var profileOpt = Optional.empty(); + var gameProfileInfo = ((GameProfileCacheAccessor) services.profileCache()).getProfilesByName().get(name.toLowerCase(Locale.ROOT)); + + if (gameProfileInfo != null) + profileOpt = Optional.of(gameProfileInfo.getProfile()); skinrestorer$replaceSkin(profileOpt, cir); } @@ -57,7 +63,7 @@ public abstract class SkullBlockEntityMixin { PlayerUtils.applyRestoredSkin(profile, skin.value()); return Optional.of(profile); - })); + }, Util.backgroundExecutor().forName("getProfile"))); } } } diff --git a/common/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java b/common/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java index 93bdbbe..5ee7122 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java +++ b/common/src/main/java/net/lionarius/skinrestorer/util/PlayerUtils.java @@ -50,14 +50,14 @@ public final class PlayerUtils { ) )); - var trackedEntity = (TrackedEntityAccessorInvoker) ((ChunkMapAccessor) chunkMap).getEntityMap().get(player.getId()); + var trackedEntity = ((ChunkMapAccessor) chunkMap).getEntityMap().get(player.getId()); if (trackedEntity != null) { var seenBy = Set.copyOf(trackedEntity.getSeenBy()); for (var observerConnection : seenBy) { var observer = observerConnection.getPlayer(); trackedEntity.invokeRemovePlayer(observer); - var trackedObserverEntity = (TrackedEntityAccessorInvoker) ((ChunkMapAccessor) chunkMap).getEntityMap().get(observer.getId()); + var trackedObserverEntity = ((ChunkMapAccessor) chunkMap).getEntityMap().get(observer.getId()); if (trackedObserverEntity != null) { trackedObserverEntity.invokeRemovePlayer(player); trackedObserverEntity.invokeUpdatePlayer(player); diff --git a/common/src/main/resources/META-INF/accesstransformer.cfg b/common/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..8c8e31a --- /dev/null +++ b/common/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1 @@ +public net.minecraft.server.players.GameProfileCache$GameProfileInfo diff --git a/common/src/main/resources/skinrestorer.accesswidener b/common/src/main/resources/skinrestorer.accesswidener new file mode 100644 index 0000000..28ef0d1 --- /dev/null +++ b/common/src/main/resources/skinrestorer.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible class net/minecraft/server/players/GameProfileCache$GameProfileInfo diff --git a/common/src/main/resources/skinrestorer.mixins.json b/common/src/main/resources/skinrestorer.mixins.json index c8366d5..1956e5a 100644 --- a/common/src/main/resources/skinrestorer.mixins.json +++ b/common/src/main/resources/skinrestorer.mixins.json @@ -10,7 +10,8 @@ "PlayerListMixin", "ServerLoginPacketListenerImplMixin", "TrackedEntityAccessorInvoker", - "SkullBlockEntityMixin" + "SkullBlockEntityMixin", + "GameProfileCacheAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 85c219d..8dee4fa 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -24,6 +24,7 @@ "${mod_id}.mixins.json", "${mod_id}.fabric.mixins.json" ], + "accessWidener": "${mod_id}.accesswidener", "depends": { "fabricloader": ">=${fabric_loader_version}", "minecraft": ">=${minecraft_version}", diff --git a/forge/build.gradle b/forge/build.gradle index 31ef1cb..c52c1d9 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -33,7 +33,7 @@ minecraft { // Automatically enable forge AccessTransformers if the file exists // This location is hardcoded in Forge and can not be changed. // https://github.com/MinecraftForge/MinecraftForge/blob/be1698bb1554f9c8fa2f58e32b9ab70bc4385e60/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java#L123 - def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') + def at = project(":common").file('src/main/resources/META-INF/accesstransformer.cfg') if (at.exists()) { accessTransformer = at } @@ -92,3 +92,7 @@ sourceSets.each { it.output.resourcesDir = dir it.java.destinationDirectory = dir } + +tasks.named('processResources') { + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 72eb58a..0385d9e 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -12,9 +12,9 @@ neoForge { version = neoforge_version // Automatically enable neoforge AccessTransformers if the file exists - def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') + def at = project(":common").file('src/main/resources/META-INF/accesstransformer.cfg') if (at.exists()) { - minecraft.accessTransformers.file(at) + accessTransformers.from(at.absolutePath) } parchment { From fb7a4d11e949f21b6b9cdbfcf689896af6c85e2f Mon Sep 17 00:00:00 2001 From: Lionarius Date: Sat, 26 Jul 2025 10:17:44 +0300 Subject: [PATCH 6/8] bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8d34a53..11b2125 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ minecraft_version_list=1.21.6,1.21.7,1.21.8 minecraft_version_range=[1.21.6, 1.22) mod_id=skinrestorer mod_name=SkinRestorer -mod_version=2.4.2 +mod_version=2.4.3 mod_author=Lionarius mod_homepage=https://modrinth.com/mod/skinrestorer mod_sources=https://github.com/Suiranoil/SkinRestorer From 583205febce60cac4c3dbafa8a46e8d718f7dcd1 Mon Sep 17 00:00:00 2001 From: Lionarius Date: Sat, 26 Jul 2025 10:18:18 +0300 Subject: [PATCH 7/8] update CHANGELOG --- CHANGELOG.md | 5 +++++ CHANGELOG_LATEST.md | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12e4da3..27ff975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.3] - 2025-07-25 +### Fixed +- Fixed crash on client when loading player head skin (fixes [#63](https://github.com/Suiranoil/SkinRestorer/issues/63) and [#64](https://github.com/Suiranoil/SkinRestorer/issues/64)) +- Fixed server freeze when loading player head skin + ## [2.4.2] - 2025-07-13 ### Fixed - Fix crash when head profile name is null (fixes [#60](https://github.com/Suiranoil/SkinRestorer/issues/60) and [#61](https://github.com/Suiranoil/SkinRestorer/issues/61)) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index bf4e1a8..21da2bd 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -1,2 +1,3 @@ ### Fixed -- Fix crash when head profile name is null (fixes [#60](https://github.com/Suiranoil/SkinRestorer/issues/60) and [#61](https://github.com/Suiranoil/SkinRestorer/issues/61)) +- Fixed crash on client when loading player head skin (fixes [#63](https://github.com/Suiranoil/SkinRestorer/issues/63) and [#64](https://github.com/Suiranoil/SkinRestorer/issues/64)) +- Fixed server freeze when loading player head skin From 5f8a868e5cd986817689b7896836b6277a282d79 Mon Sep 17 00:00:00 2001 From: Lionarius Date: Sun, 17 Aug 2025 18:28:46 +0300 Subject: [PATCH 8/8] backport to 1.21 --- .../net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java index 21d4d38..f4ae057 100644 --- a/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java +++ b/common/src/main/java/net/lionarius/skinrestorer/mixin/SkullBlockEntityMixin.java @@ -63,7 +63,7 @@ public abstract class SkullBlockEntityMixin { PlayerUtils.applyRestoredSkin(profile, skin.value()); return Optional.of(profile); - }, Util.backgroundExecutor().forName("getProfile"))); + }, Util.backgroundExecutor())); } } }