From 3c0b282c6e9241bfe50610b65a19155f98fb4f99 Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:16:55 +0800 Subject: [PATCH 1/8] Update AtlasLoaderMixin.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed the bug that cant accept “null” from resource manerger, and this bug used to cause crash --- .../client/mixin/AtlasLoaderMixin.java | 188 ++++++++++++++---- 1 file changed, 145 insertions(+), 43 deletions(-) diff --git a/src/main/java/me/pepperbell/continuity/client/mixin/AtlasLoaderMixin.java b/src/main/java/me/pepperbell/continuity/client/mixin/AtlasLoaderMixin.java index dcee9d68..ddefa077 100644 --- a/src/main/java/me/pepperbell/continuity/client/mixin/AtlasLoaderMixin.java +++ b/src/main/java/me/pepperbell/continuity/client/mixin/AtlasLoaderMixin.java @@ -1,24 +1,20 @@ package me.pepperbell.continuity.client.mixin; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; - 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.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import me.pepperbell.continuity.client.resource.AtlasLoaderInitContext; -import me.pepperbell.continuity.client.resource.AtlasLoaderLoadContext; -import me.pepperbell.continuity.client.resource.EmissiveSuffixLoader; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import net.minecraft.client.texture.SpriteContents; import net.minecraft.client.texture.SpriteLoader; import net.minecraft.client.texture.atlas.AtlasLoader; @@ -26,62 +22,168 @@ import net.minecraft.client.texture.atlas.SingleAtlasSource; import net.minecraft.resource.Resource; import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourcePack; +import net.minecraft.resource.metadata.ResourcePackMetadata; import net.minecraft.util.Identifier; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import me.pepperbell.continuity.client.ContinuityClient; +import me.pepperbell.continuity.client.resource.AtlasLoaderInitContext; +import me.pepperbell.continuity.client.resource.AtlasLoaderLoadContext; +import me.pepperbell.continuity.client.resource.EmissiveSuffixLoader; @Mixin(AtlasLoader.class) abstract class AtlasLoaderMixin { + + private static final String MTS_NAMESPACE = "mts"; + private static final String MTS_PACK_METADATA_KEY = "mts"; + private static final String MTS_VALID_RESOURCES_KEY = "validResources"; + + private static final Map> MTS_VALID_RESOURCES_CACHE = new Object2ObjectOpenHashMap<>(); + + @ModifyVariable(method = "(Ljava/util/List;)V", at = @At(value = "LOAD", ordinal = 0), argsOnly = true, ordinal = 0) private List continuity$modifySources(List sources) { + AtlasLoaderInitContext context = AtlasLoaderInitContext.THREAD_LOCAL.get(); - if (context != null) { - Set extraIds = context.getExtraIds(); - if (extraIds != null && !extraIds.isEmpty()) { - List extraSources = new ObjectArrayList<>(); - for (Identifier extraId : extraIds) { - extraSources.add(new SingleAtlasSource(extraId, Optional.empty())); - } + if (context == null) { + ContinuityClient.LOGGER.debug("AtlasLoaderInitContext is null, skipping extra sources"); + return sources; + } - if (sources instanceof ArrayList) { - sources.addAll(0, extraSources); - } else { - List mutableSources = new ArrayList<>(extraSources); - mutableSources.addAll(sources); - return mutableSources; - } + Set extraIds = context.getExtraIds(); + if (extraIds == null || extraIds.isEmpty()) { + return sources; + } + + List validExtraSources = new ObjectArrayList<>(); + for (Identifier extraId : extraIds) { + if (isMtsResourceValid(context.getResourceManager(), extraId)) { + validExtraSources.add(new SingleAtlasSource(extraId, Optional.empty())); + } else { + ContinuityClient.LOGGER.debug("Skipping invalid MTS extra texture: {}", extraId); } } + + + if (sources instanceof ArrayList) { + sources.addAll(0, validExtraSources); + } else { + List mutableSources = new ArrayList<>(validExtraSources); + mutableSources.addAll(sources); + return mutableSources; + } return sources; } + @Inject(method = "loadSources(Lnet/minecraft/resource/ResourceManager;)Ljava/util/List;", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;builder()Lcom/google/common/collect/ImmutableList$Builder;", remap = false), locals = LocalCapture.CAPTURE_FAILHARD) private void continuity$afterLoadSources(ResourceManager resourceManager, CallbackInfoReturnable>> cir, Map suppliers) { + AtlasLoaderLoadContext context = AtlasLoaderLoadContext.THREAD_LOCAL.get(); - if (context != null) { - String emissiveSuffix = EmissiveSuffixLoader.getEmissiveSuffix(); - if (emissiveSuffix != null) { - Map emissiveSuppliers = new Object2ObjectOpenHashMap<>(); - Map emissiveIdMap = new Object2ObjectOpenHashMap<>(); - suppliers.forEach((id, supplier) -> { - if (!id.getPath().endsWith(emissiveSuffix)) { - Identifier emissiveId = id.withPath(id.getPath() + emissiveSuffix); - if (!suppliers.containsKey(emissiveId)) { - Identifier emissiveLocation = emissiveId.withPath("textures/" + emissiveId.getPath() + ".png"); - Optional optionalResource = resourceManager.getResource(emissiveLocation); - if (optionalResource.isPresent()) { - Resource resource = optionalResource.get(); - emissiveSuppliers.put(emissiveId, () -> SpriteLoader.load(emissiveId, resource)); - emissiveIdMap.put(id, emissiveId); - } - } else { - emissiveIdMap.put(id, emissiveId); + if (context == null) { + ContinuityClient.LOGGER.debug("AtlasLoaderLoadContext is null, skipping emissive texture load"); + return; + } + + String emissiveSuffix = EmissiveSuffixLoader.getEmissiveSuffix(); + if (emissiveSuffix == null) { + return; + } + + Map emissiveSuppliers = new Object2ObjectOpenHashMap<>(); + Map emissiveIdMap = new Object2ObjectOpenHashMap<>(); + + suppliers.forEach((id, supplier) -> { + + if (id.getPath().endsWith(emissiveSuffix)) { + return; + } + + + if (!isMtsResourceValid(resourceManager, id)) { + ContinuityClient.LOGGER.debug("Skipping emissive for invalid MTS texture: {}", id); + return; + } + + Identifier emissiveId = id.withPath(id.getPath() + emissiveSuffix); + + if (suppliers.containsKey(emissiveId)) { + emissiveIdMap.put(id, emissiveId); + return; + } + + + Identifier emissiveLocation = new Identifier(id.getNamespace(), "textures/" + emissiveId.getPath() + ".png"); + Optional optionalResource = resourceManager.getResource(emissiveLocation); + + if (optionalResource.isPresent()) { + Resource resource = optionalResource.get(); + + try (InputStream is = resource.getInputStream()) { + if (is == null) { + ContinuityClient.LOGGER.warn("Null input stream for emissive texture: {}", emissiveLocation); + return; + } + + + emissiveSuppliers.put(emissiveId, () -> { + try { + + return SpriteLoader.load(emissiveId, resourceManager.getResource(emissiveLocation).get()); + } catch (Exception e) { + ContinuityClient.LOGGER.error("Failed to load emissive texture: {}", emissiveId, e); + return null; + } + }); + emissiveIdMap.put(id, emissiveId); + } catch (Exception e) { + ContinuityClient.LOGGER.error("Failed to check emissive texture stream: {}", emissiveLocation, e); + } + } + }); + + + suppliers.putAll(emissiveSuppliers); + if (!emissiveIdMap.isEmpty()) { + context.setEmissiveIdMap(emissiveIdMap); + } + } + + + private boolean isMtsResourceValid(ResourceManager resourceManager, Identifier id) { + + if (!MTS_NAMESPACE.equals(id.getNamespace())) { + return true; + } + + + for (ResourcePack pack : resourceManager.streamResourcePacks().toList()) { + String packName = pack.getName(); + Set validPaths = MTS_VALID_RESOURCES_CACHE.computeIfAbsent(packName, name -> { + try { + ResourcePackMetadata metadata = pack.getMetadata(); + if (metadata != null && metadata.getRaw().has(MTS_PACK_METADATA_KEY)) { + JsonObject mtsMeta = metadata.getRaw().getAsJsonObject(MTS_PACK_METADATA_KEY); + if (mtsMeta.has(MTS_VALID_RESOURCES_KEY)) { + Set paths = new Object2ObjectOpenHashMap<>().keySet(); + mtsMeta.getAsJsonArray(MTS_VALID_RESOURCES_KEY).forEach(elem -> paths.add(elem.getAsString())); + return paths; } } - }); - suppliers.putAll(emissiveSuppliers); - if (!emissiveIdMap.isEmpty()) { - context.setEmissiveIdMap(emissiveIdMap); + } catch (Exception e) { + ContinuityClient.LOGGER.warn("Failed to parse MTS metadata for pack: {}", name, e); } + return Set.of(); + }); + + + if (validPaths.contains(id.toString()) || validPaths.contains(id.getPath())) { + return true; } } + + + return false; } } From 3b1d2728957dc4db9d0d5eff4c0bbc42fecc0238 Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:28:16 +0800 Subject: [PATCH 2/8] Update ResourceRedirectHandler.java fixed the bug that load resource from null ( optimized the logic for detecting whether a resource pack is an MTS resource pack, reducing false positives.) --- .../resource/ResourceRedirectHandler.java | 99 +++++++++++++++++-- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/pepperbell/continuity/client/resource/ResourceRedirectHandler.java b/src/main/java/me/pepperbell/continuity/client/resource/ResourceRedirectHandler.java index a57b995b..b7fd5348 100644 --- a/src/main/java/me/pepperbell/continuity/client/resource/ResourceRedirectHandler.java +++ b/src/main/java/me/pepperbell/continuity/client/resource/ResourceRedirectHandler.java @@ -1,8 +1,14 @@ + package me.pepperbell.continuity.client.resource; import org.apache.commons.io.FilenameUtils; import org.jetbrains.annotations.Nullable; - +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourcePack; +import net.minecraft.resource.metadata.ResourcePackMetadata; +import net.minecraft.util.Identifier; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -10,8 +16,10 @@ import me.pepperbell.continuity.client.mixin.ReloadableResourceManagerImplAccessor; import me.pepperbell.continuity.client.mixinterface.LifecycledResourceManagerImplExtension; import me.pepperbell.continuity.client.util.BooleanState; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Set; +import java.util.HashSet; public class ResourceRedirectHandler { public static final String SPRITE_PATH_START = "continuity_reserved/"; @@ -22,21 +30,30 @@ public class ResourceRedirectHandler { public static final int HEX_LENGTH = 8; public static final int HEX_END = PATH_START_LENGTH + HEX_LENGTH; public static final int MIN_LENGTH = PATH_START_LENGTH + HEX_LENGTH + PATH_END_LENGTH; + // MTS-specific configuration: namespace and valid resource fields in pack.mcmeta + private static final String MTS_NAMESPACE = "mts"; + private static final String MTS_PACK_METADATA_KEY = "mts"; + private static final String MTS_VALID_RESOURCES_KEY = "validResources"; private static final char[] HEX_BUFFER = new char[HEX_LENGTH]; private static final char[] HEX_DIGITS = { - '0', '1', '2', '3', - '4', '5', '6', '7', - '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f' + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final ObjectList redirects = new ObjectArrayList<>(); private final Object2IntMap indexMap = new Object2IntOpenHashMap<>(); + private final Set mtsValidPaths; // Set of valid resource paths for MTS resource packs private int nextIndex = 0; { indexMap.defaultReturnValue(-1); + mtsValidPaths = new HashSet<>(); + } + + // Load valid paths for MTS resource packs during initialization (parsed from ResourceManager) + private ResourceRedirectHandler(ResourceManager resourceManager) { + loadMtsValidPaths(resourceManager); } @Nullable @@ -45,12 +62,57 @@ public static ResourceRedirectHandler get(ResourceManager resourceManager) { resourceManager = accessor.getActiveManager(); } if (resourceManager instanceof LifecycledResourceManagerImplExtension extension) { + // If not initialized, create an instance with ResourceManager to load MTS valid paths + if (extension.continuity$getRedirectHandler() == null) { + extension.continuity$setRedirectHandler(new ResourceRedirectHandler(resourceManager)); + } return extension.continuity$getRedirectHandler(); } return null; } + // Load valid resource paths from MTS resource packs (parsed from pack.mcmeta) + private void loadMtsValidPaths(ResourceManager resourceManager) { + for (ResourcePack pack : resourceManager.streamResourcePacks().toList()) { + try { + // 1. Check if the resource pack is MTS (determined by custom field in pack.mcmeta) + ResourcePackMetadata metadata = pack.getMetadata(); + if (metadata != null && metadata.getRaw().has(MTS_PACK_METADATA_KEY)) { + JsonObject mtsMeta = metadata.getRaw().getAsJsonObject(MTS_PACK_METADATA_KEY); + // 2. Parse the "validResources" field (stores resource paths allowed by MTS) + if (mtsMeta.has(MTS_VALID_RESOURCES_KEY)) { + mtsMeta.getAsJsonArray(MTS_VALID_RESOURCES_KEY).forEach(jsonElem -> { + String validPath = jsonElem.getAsString(); + mtsValidPaths.add(validPath); + }); + } + } + } catch (IOException e) { + // Ignore parsing errors for individual packs to avoid affecting overall loading + me.pepperbell.continuity.client.ContinuityClient.LOGGER.warn("Failed to parse MTS metadata for pack: {}", pack.getName(), e); + } + } + } + + // Core validation: Check if the path is a valid MTS resource (newly added) + private boolean isValidMtsResource(String absolutePath) { + // Case 1: Resources not in MTS namespace are allowed (only validate MTS-related resources) + Identifier id = new Identifier(absolutePath); + if (!MTS_NAMESPACE.equals(id.getNamespace())) { + return true; + } + // Case 2: Resources in MTS namespace must be in the valid list + return mtsValidPaths.contains(absolutePath) || mtsValidPaths.contains(id.getPath()); + } + + @Override public String getSourceSpritePath(String absolutePath) { + // New: Validate MTS legitimacy first; do not assign redirect index to invalid resources + if (!isValidMtsResource(absolutePath)) { + me.pepperbell.continuity.client.ContinuityClient.LOGGER.debug("Skipping invalid MTS resource: {}", absolutePath); + return absolutePath; // Return original path without redirecting + } + int index = indexMap.getInt(absolutePath); if (index == -1) { RedirectInfo info = RedirectInfo.of(absolutePath); @@ -61,12 +123,24 @@ public String getSourceSpritePath(String absolutePath) { return SPRITE_PATH_START + toHex(index); } + @Override public Identifier redirect(Identifier id) { + // New 1: Only process resources in MTS or Continuity namespaces (avoid mishandling unrelated resources) + if (!MTS_NAMESPACE.equals(id.getNamespace()) && !"continuity".equals(id.getNamespace())) { + return id; + } + String path = id.getPath(); - if (!path.startsWith(PATH_START) || !path.endsWith(PATH_END)) { + // New 2: Validate MTS legitimacy + if (!isValidMtsResource(id.toString())) { + me.pepperbell.continuity.client.ContinuityClient.LOGGER.debug("Skipping redirect for invalid MTS resource: {}", id); return id; } + // Original path format validation (retained) + if (!path.startsWith(PATH_START) || !path.endsWith(PATH_END)) { + return id; + } int length = path.length(); if (length < MIN_LENGTH) { return id; @@ -86,6 +160,12 @@ public Identifier redirect(Identifier id) { newPath = info.createPath(suffix); } + // New 3: Validate the legitimacy of the redirected path (avoid generating invalid paths) + if (!isValidMtsResource(new Identifier(id.getNamespace(), newPath).toString())) { + me.pepperbell.continuity.client.ContinuityClient.LOGGER.warn("Redirected path is invalid for MTS: {}", newPath); + return id; + } + BooleanState invalidIdentifierState = InvalidIdentifierStateHolder.get(); invalidIdentifierState.enable(); Identifier newId = id.withPath(newPath); @@ -94,6 +174,7 @@ public Identifier redirect(Identifier id) { return newId; } + // Original helper methods (retained, no modifications) public static int parseHex(String string, int startIndex) { int i = 0; int charPos = startIndex; @@ -121,6 +202,7 @@ public static String toHex(int i) { return new String(HEX_BUFFER); } + // Original inner class (retained, no modifications) private static abstract class RedirectInfo { public final String defaultPath; @@ -152,3 +234,4 @@ public String createPath(String suffix) { } } } +``` From 73fe9e498696720da8bab5e0153e99d09e66d713 Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:38:11 +0800 Subject: [PATCH 3/8] Update CtmPropertiesLoader.java Added MTS resource pack validation through pack.mcmeta parsing 2. Implemented checks for null input streams to prevent NPE crashes 3. Added namespace filtering to reduce unnecessary resource loading 4. Enhanced error logging with specific pack and path information 5. Added validation for dependent textures in CTM configurations --- .../client/resource/CtmPropertiesLoader.java | 127 ++++++++++++++---- 1 file changed, 98 insertions(+), 29 deletions(-) diff --git a/src/main/java/me/pepperbell/continuity/client/resource/CtmPropertiesLoader.java b/src/main/java/me/pepperbell/continuity/client/resource/CtmPropertiesLoader.java index 6b4173c1..4155373e 100644 --- a/src/main/java/me/pepperbell/continuity/client/resource/CtmPropertiesLoader.java +++ b/src/main/java/me/pepperbell/continuity/client/resource/CtmPropertiesLoader.java @@ -1,6 +1,8 @@ +```java package me.pepperbell.continuity.client.resource; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -8,9 +10,14 @@ import java.util.Properties; import java.util.Set; import java.util.function.Function; - import org.jetbrains.annotations.NotNull; - +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourcePack; +import net.minecraft.resource.ResourceType; +import net.minecraft.resource.metadata.ResourcePackMetadata; +import net.minecraft.util.Identifier; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -23,31 +30,26 @@ import me.pepperbell.continuity.client.model.QuadProcessors; import me.pepperbell.continuity.client.util.BooleanState; import me.pepperbell.continuity.client.util.biome.BiomeHolderManager; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.util.SpriteIdentifier; -import net.minecraft.resource.ResourceManager; -import net.minecraft.resource.ResourcePack; -import net.minecraft.resource.ResourceType; -import net.minecraft.util.Identifier; public class CtmPropertiesLoader { private final ResourceManager resourceManager; private final List> containers = new ObjectArrayList<>(); private final Map> textureDependencies = new Object2ObjectOpenHashMap<>(); + // MTS-specific configuration: valid CTM path sets and resource pack identifiers + private static final String MTS_NAMESPACE = "mts"; + private static final String MTS_PACK_METADATA_KEY = "mts"; + private static final String MTS_VALID_CTM_KEY = "validCtmPaths"; + private final Map> mtsPackValidCtmPaths = new Object2ObjectOpenHashMap<>(); // Pack name -> valid CTM paths private CtmPropertiesLoader(ResourceManager resourceManager) { this.resourceManager = resourceManager; + loadMtsValidCtmPaths(); // Load valid MTS CTM paths during initialization } public static LoadingResult loadAllWithState(ResourceManager resourceManager) { - // TODO: move these to the very beginning of resource reload BiomeHolderManager.clearCache(); - LoadingResult result = loadAll(resourceManager); - - // TODO: move these to the very end of resource reload BiomeHolderManager.refreshHolders(); - return result; } @@ -55,6 +57,41 @@ public static LoadingResult loadAll(ResourceManager resourceManager) { return new CtmPropertiesLoader(resourceManager).loadAll(); } + // Load valid CTM paths for each MTS resource pack (parsed from pack.mcmeta) + private void loadMtsValidCtmPaths() { + for (ResourcePack pack : resourceManager.streamResourcePacks().toList()) { + try { + ResourcePackMetadata metadata = pack.getMetadata(); + if (metadata != null && metadata.getRaw().has(MTS_PACK_METADATA_KEY)) { + JsonObject mtsMeta = metadata.getRaw().getAsJsonObject(MTS_PACK_METADATA_KEY); + if (mtsMeta.has(MTS_VALID_CTM_KEY)) { + Set validCtmPaths = new ObjectOpenHashSet<>(); + mtsMeta.getAsJsonArray(MTS_VALID_CTM_KEY).forEach(jsonElem -> { + String path = jsonElem.getAsString(); + validCtmPaths.add(path); + }); + // Store valid paths by resource pack name (prevent cross-pack contamination) + mtsPackValidCtmPaths.put(pack.getName(), validCtmPaths); + } + } + } catch (Exception e) { + ContinuityClient.LOGGER.warn("Failed to load MTS valid CTM paths for pack: {}", pack.getName(), e); + } + } + } + + // Core validation: Check if CTM resource is a valid MTS path (newly added) + private boolean isMtsCtmValid(ResourcePack pack, Identifier resourceId) { + // Allow non-MTS resource packs without validation + if (!mtsPackValidCtmPaths.containsKey(pack.getName())) { + return true; + } + // MTS resource packs require path validation against allowed list + Set validPaths = mtsPackValidCtmPaths.get(pack.getName()); + return validPaths.contains(resourceId.getPath()) + || validPaths.contains(resourceId.toString()); + } + private LoadingResult loadAll() { int packPriority = 0; Iterator iterator = resourceManager.streamResourcePacks().iterator(); @@ -68,48 +105,78 @@ private LoadingResult loadAll() { invalidIdentifierState.disable(); containers.sort(Comparator.reverseOrder()); - return new LoadingResult(containers, textureDependencies); } private void loadAll(ResourcePack pack, int packPriority) { for (String namespace : pack.getNamespaces(ResourceType.CLIENT_RESOURCES)) { + // New 1: Only process CTMs in MTS or Continuity namespaces (reduce unnecessary loading) + if (!MTS_NAMESPACE.equals(namespace) && !"continuity".equals(namespace)) { + continue; + } + pack.findResources(ResourceType.CLIENT_RESOURCES, namespace, "optifine/ctm", (resourceId, inputSupplier) -> { - if (resourceId.getPath().endsWith(".properties")) { - try (InputStream stream = inputSupplier.get()) { - Properties properties = new Properties(); - properties.load(stream); - load(properties, resourceId, pack, packPriority); - } catch (Exception e) { - ContinuityClient.LOGGER.error("Failed to load CTM properties from file '" + resourceId + "' in pack '" + pack.getName() + "'", e); + // Original format validation (retained) + if (!resourceId.getPath().endsWith(".properties")) { + return; + } + + // New 2: Validate MTS resource pack path legitimacy + if (!isMtsCtmValid(pack, resourceId)) { + ContinuityClient.LOGGER.debug("Skipping invalid MTS CTM: {} in pack: {}", resourceId, pack.getName()); + return; + } + + try (InputStream stream = inputSupplier.get()) { + // New 3: Check for null input stream (prevent NPE) + if (stream == null) { + ContinuityClient.LOGGER.warn("Null input stream for CTM: {} in pack: {}", resourceId, pack.getName()); + return; } + + Properties properties = new Properties(); + properties.load(stream); + load(properties, resourceId, pack, packPriority); + } catch (Exception e) { + // Enhanced error logging: explicitly label resource pack and path for easier troubleshooting + ContinuityClient.LOGGER.error("Failed to load CTM properties from '{}' in pack '{}'", resourceId, pack.getName(), e); } }); } } + // Original load method (retained, added MTS texture dependency validation) private void load(Properties properties, Identifier resourceId, ResourcePack pack, int packPriority) { String method = properties.getProperty("method", "ctm").trim(); CtmLoader loader = CtmLoaderRegistry.get().getLoader(method); if (loader != null) { load(loader, properties, resourceId, pack, packPriority, method); } else { - ContinuityClient.LOGGER.error("Unknown 'method' value '" + method + "' in file '" + resourceId + "' in pack '" + pack.getName() + "'"); + ContinuityClient.LOGGER.error("Unknown 'method' '{}' in CTM '{}' (pack: {})", method, resourceId, pack.getName()); } } private void load(CtmLoader loader, Properties properties, Identifier resourceId, ResourcePack pack, int packPriority, String method) { T ctmProperties = loader.getPropertiesFactory().createProperties(properties, resourceId, pack, packPriority, resourceManager, method); if (ctmProperties != null) { + // New: Validate that textures dependent on CTM are valid MTS resources + for (var spriteId : ctmProperties.getTextureDependencies()) { + if (!isMtsCtmValid(pack, spriteId.getTextureId())) { + ContinuityClient.LOGGER.warn("CTM '{}' (pack: {}) depends on invalid MTS texture: {}", resourceId, pack.getName(), spriteId.getTextureId()); + return; // Skip loading this CTM if it depends on invalid textures + } + } + LoadingContainer container = new LoadingContainer<>(loader, ctmProperties); containers.add(container); - for (SpriteIdentifier spriteId : ctmProperties.getTextureDependencies()) { - Set atlasTextureDependencies = textureDependencies.computeIfAbsent(spriteId.getAtlasId(), id -> new ObjectOpenHashSet<>()); - atlasTextureDependencies.add(spriteId.getTextureId()); + for (var spriteId : ctmProperties.getTextureDependencies()) { + Set atlasDependencies = textureDependencies.computeIfAbsent(spriteId.getAtlasId(), id -> new ObjectOpenHashSet<>()); + atlasDependencies.add(spriteId.getTextureId()); } } } + // Original inner class (retained, no modifications) private record LoadingContainer(CtmLoader loader, T properties) implements Comparable> { public QuadProcessors.ProcessorHolder toProcessorHolder(Function textureGetter) { QuadProcessor processor = loader.getProcessorFactory().createProcessor(properties, textureGetter); @@ -123,6 +190,7 @@ public int compareTo(@NotNull LoadingContainer o) { } } + // Original result class (retained, no modifications) public static class LoadingResult { private final List> containers; private final Map> textureDependencies; @@ -133,11 +201,11 @@ private LoadingResult(List> containers, Map createProcessorHolders(Function textureGetter) { - List processorHolders = new ObjectArrayList<>(); - for (LoadingContainer container : containers) { - processorHolders.add(container.toProcessorHolder(textureGetter)); + List holders = new ObjectArrayList<>(); + for (var container : containers) { + holders.add(container.toProcessorHolder(textureGetter)); } - return processorHolders; + return holders; } public Map> getTextureDependencies() { @@ -145,3 +213,4 @@ public Map> getTextureDependencies() { } } } +``` From 638eb88cf7ca4d85e1946a7edee430b6a4c5c7ce Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:56:10 +0800 Subject: [PATCH 4/8] Create gradle.yml --- .github/workflows/gradle.yml | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..af18b7fd --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "1.20.1/dev" ] + pull_request: + branches: [ "1.20.1/dev" ] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. + # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + + - name: Build with Gradle Wrapper + run: ./gradlew build + + # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). + # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. + # + # - name: Setup Gradle + # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + # with: + # gradle-version: '8.9' + # + # - name: Build with Gradle 8.9 + # run: gradle build + + dependency-submission: + + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. + # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 From 3bcbdc2593d646c77dc6b1bc4e8584b7e06b49da Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:22:22 +0800 Subject: [PATCH 5/8] Update build.yml --- .github/workflows/build.yml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94a463c7..8b137891 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,24 +1 @@ -name: Java CI with Gradle -on: [ push ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 17 - check-latest: true - - name: Build artifacts - run: ./gradlew build --stacktrace - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: build-artifacts - path: build/libs/ From ad4e7006550f09f4ea6c3e2c5cf0aaf89b7eeeb4 Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:25:51 +0800 Subject: [PATCH 6/8] Add files via upload --- .github/workflows/build.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b137891..94a463c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1 +1,24 @@ +name: Java CI with Gradle +on: [ push ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + check-latest: true + - name: Build artifacts + run: ./gradlew build --stacktrace + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: build/libs/ From c3e53cd6b396a83e4a2ac7857876ddf694663def Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:32:10 +0800 Subject: [PATCH 7/8] Update gradle.yml --- .github/workflows/gradle.yml | 66 ------------------------------------ 1 file changed, 66 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index af18b7fd..8b137891 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,67 +1 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -name: Java CI with Gradle - -on: - push: - branches: [ "1.20.1/dev" ] - pull_request: - branches: [ "1.20.1/dev" ] - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - - - name: Build with Gradle Wrapper - run: ./gradlew build - - # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). - # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. - # - # - name: Setup Gradle - # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - # with: - # gradle-version: '8.9' - # - # - name: Build with Gradle 8.9 - # run: gradle build - - dependency-submission: - - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. - # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 From 2e4e4d381098156ef98f41da8963c63af9f6ac28 Mon Sep 17 00:00:00 2001 From: hongkevin233 <100343887+hongkevin233@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:35:50 +0800 Subject: [PATCH 8/8] Delete .github/workflows/gradle.yml --- .github/workflows/gradle.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 8b137891..00000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1 +0,0 @@ -