|
1 | 1 | package jp.s12kuma01.celeritasextra.client.particle; |
2 | 2 |
|
| 3 | +import jp.s12kuma01.celeritasextra.mixin.particle.IMixinParticleManager; |
| 4 | +import net.minecraft.client.particle.IParticleFactory; |
3 | 5 | import net.minecraft.client.particle.Particle; |
4 | | -import net.minecraftforge.fml.common.Loader; |
5 | | -import net.minecraftforge.fml.common.ModContainer; |
6 | | -import org.objectweb.asm.ClassReader; |
7 | | - |
8 | | -import java.io.File; |
9 | | -import java.io.IOException; |
10 | | -import java.io.InputStream; |
11 | | -import java.nio.file.FileVisitResult; |
12 | | -import java.nio.file.Files; |
13 | | -import java.nio.file.Path; |
14 | | -import java.nio.file.SimpleFileVisitor; |
15 | | -import java.nio.file.attribute.BasicFileAttributes; |
| 6 | +import net.minecraft.client.particle.ParticleManager; |
| 7 | + |
| 8 | +import java.lang.reflect.Method; |
16 | 9 | import java.util.*; |
17 | 10 | import java.util.concurrent.ConcurrentHashMap; |
18 | | -import java.util.jar.JarEntry; |
19 | | -import java.util.jar.JarFile; |
20 | 11 |
|
21 | 12 | /** |
22 | 13 | * Runtime registry for discovered particle classes. |
@@ -122,126 +113,36 @@ public String[] getDiscoveredClassesArray() { |
122 | 113 | } |
123 | 114 |
|
124 | 115 | /** |
125 | | - * Scan all mod jar files using ASM to discover Particle subclasses |
126 | | - * without loading or instantiating them. Uses bytecode superclass analysis |
127 | | - * to find classes that extend Particle or any known Particle subclass. |
128 | | - * Also records which mod each class belongs to. |
| 116 | + * Scan ParticleManager's registered factories to discover particle classes. |
| 117 | + * Uses two strategies: |
| 118 | + * 1. Enclosing class - for inner class factories (e.g. ParticleFlame.Factory) |
| 119 | + * 2. Covariant return type - for factories that declare a specific Particle subclass return type |
129 | 120 | */ |
130 | | - public void scanModJars() { |
131 | | - // Build initial set of known particle class names (internal form: / separator) |
132 | | - Set<String> knownParticleNames = new HashSet<>(); |
133 | | - knownParticleNames.add(Particle.class.getName().replace('.', '/')); |
134 | | - for (String className : discoveredClasses.keySet()) { |
135 | | - knownParticleNames.add(className.replace('.', '/')); |
136 | | - } |
137 | | - |
138 | | - // Collect superclass map and class-to-mod mapping from mod sources using ASM |
139 | | - Map<String, String> superMap = new HashMap<>(); |
140 | | - Map<String, String> classToModName = new HashMap<>(); |
141 | | - Set<File> scannedSources = new HashSet<>(); |
142 | | - |
143 | | - for (ModContainer mod : Loader.instance().getModList()) { |
144 | | - File source = mod.getSource(); |
145 | | - if (source == null || !scannedSources.add(source)) continue; |
146 | | - |
147 | | - String modName = mod.getModId(); |
148 | | - |
149 | | - if (source.isFile() && source.getName().endsWith(".jar")) { |
150 | | - scanJarForSuperclasses(source, superMap, classToModName, modName); |
151 | | - } else if (source.isDirectory()) { |
152 | | - scanDirectoryForSuperclasses(source, superMap, classToModName, modName); |
| 121 | + public void scanFactories(ParticleManager particleManager) { |
| 122 | + Map<Integer, IParticleFactory> factories = ((IMixinParticleManager) particleManager).getParticleTypes(); |
| 123 | + |
| 124 | + for (var entry : factories.entrySet()) { |
| 125 | + IParticleFactory factory = entry.getValue(); |
| 126 | + Class<?> factoryClass = factory.getClass(); |
| 127 | + |
| 128 | + // Strategy 1: Enclosing class (inner class factories like ParticleFlame.Factory) |
| 129 | + Class<?> enclosingClass = factoryClass.getEnclosingClass(); |
| 130 | + if (enclosingClass != null && Particle.class.isAssignableFrom(enclosingClass)) { |
| 131 | + recordClass(enclosingClass.getName(), enclosingClass.getSimpleName()); |
| 132 | + continue; |
153 | 133 | } |
154 | | - } |
155 | 134 |
|
156 | | - // Fallback: scan the jar/directory containing Particle.class itself. |
157 | | - // MinecraftDummyContainer.getSource() returns a non-existent "minecraft.jar", |
158 | | - // so vanilla Particle subclasses are missed by the mod list scan above. |
159 | | - try { |
160 | | - var codeSource = Particle.class.getProtectionDomain().getCodeSource(); |
161 | | - if (codeSource != null) { |
162 | | - File particleSource = new File(codeSource.getLocation().toURI()); |
163 | | - if (scannedSources.add(particleSource)) { |
164 | | - if (particleSource.isFile() && particleSource.getName().endsWith(".jar")) { |
165 | | - scanJarForSuperclasses(particleSource, superMap, classToModName, "minecraft"); |
166 | | - } else if (particleSource.isDirectory()) { |
167 | | - scanDirectoryForSuperclasses(particleSource, superMap, classToModName, "minecraft"); |
| 135 | + // Strategy 2: Covariant return type analysis |
| 136 | + try { |
| 137 | + for (Method method : factoryClass.getDeclaredMethods()) { |
| 138 | + Class<?> returnType = method.getReturnType(); |
| 139 | + if (returnType != Particle.class && Particle.class.isAssignableFrom(returnType)) { |
| 140 | + recordClass(returnType.getName(), returnType.getSimpleName()); |
| 141 | + break; |
168 | 142 | } |
169 | 143 | } |
| 144 | + } catch (Throwable _) { |
170 | 145 | } |
171 | | - } catch (Exception ignored) { |
172 | | - } |
173 | | - |
174 | | - // Assign mod names to already-discovered classes (from config or runtime recording) |
175 | | - for (String fullName : discoveredClasses.keySet()) { |
176 | | - String internalName = fullName.replace('.', '/'); |
177 | | - String modName = classToModName.get(internalName); |
178 | | - if (modName != null) { |
179 | | - recordModName(fullName, modName); |
180 | | - } |
181 | | - } |
182 | | - |
183 | | - // Iteratively resolve: find classes whose superclass is a known particle class |
184 | | - boolean changed = true; |
185 | | - while (changed) { |
186 | | - changed = false; |
187 | | - for (var entry : superMap.entrySet()) { |
188 | | - String className = entry.getKey(); |
189 | | - String superName = entry.getValue(); |
190 | | - if (knownParticleNames.contains(className)) continue; |
191 | | - if (superName != null && knownParticleNames.contains(superName)) { |
192 | | - String fullName = className.replace('/', '.'); |
193 | | - String simpleName = toSimpleName(fullName); |
194 | | - recordClass(fullName, simpleName); |
195 | | - String modName = classToModName.get(className); |
196 | | - if (modName != null) { |
197 | | - recordModName(fullName, modName); |
198 | | - } |
199 | | - knownParticleNames.add(className); |
200 | | - changed = true; |
201 | | - } |
202 | | - } |
203 | | - } |
204 | | - } |
205 | | - |
206 | | - private void scanJarForSuperclasses(File jarFile, Map<String, String> superMap, |
207 | | - Map<String, String> classToModName, String modName) { |
208 | | - try (JarFile jar = new JarFile(jarFile)) { |
209 | | - Enumeration<JarEntry> entries = jar.entries(); |
210 | | - while (entries.hasMoreElements()) { |
211 | | - JarEntry entry = entries.nextElement(); |
212 | | - if (!entry.getName().endsWith(".class")) continue; |
213 | | - try (InputStream is = jar.getInputStream(entry)) { |
214 | | - ClassReader cr = new ClassReader(is); |
215 | | - String className = cr.getClassName(); |
216 | | - superMap.put(className, cr.getSuperName()); |
217 | | - classToModName.put(className, modName); |
218 | | - } catch (Throwable _) { |
219 | | - } |
220 | | - } |
221 | | - } catch (Throwable _) { |
222 | | - } |
223 | | - } |
224 | | - |
225 | | - private void scanDirectoryForSuperclasses(File dir, Map<String, String> superMap, |
226 | | - Map<String, String> classToModName, String modName) { |
227 | | - Path root = dir.toPath(); |
228 | | - try { |
229 | | - Files.walkFileTree(root, new SimpleFileVisitor<Path>() { |
230 | | - @Override |
231 | | - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { |
232 | | - if (file.toString().endsWith(".class")) { |
233 | | - try (InputStream is = Files.newInputStream(file)) { |
234 | | - ClassReader cr = new ClassReader(is); |
235 | | - String className = cr.getClassName(); |
236 | | - superMap.put(className, cr.getSuperName()); |
237 | | - classToModName.put(className, modName); |
238 | | - } catch (Throwable _) { |
239 | | - } |
240 | | - } |
241 | | - return FileVisitResult.CONTINUE; |
242 | | - } |
243 | | - }); |
244 | | - } catch (IOException ignored) { |
245 | 146 | } |
246 | 147 | } |
247 | 148 |
|
|
0 commit comments