Skip to content

Commit bce75d4

Browse files
committed
dupe mod checker
1 parent 88fbbb2 commit bce75d4

File tree

5 files changed

+283
-35
lines changed

5 files changed

+283
-35
lines changed

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
id "net.kyori.blossom" version "1.3.0"
1010
}
1111

12-
version = "1.3.7"
12+
version = "1.3.8"
1313
group = "cc.woverflow"
1414
archivesBaseName = "CrashPatch"
1515

@@ -29,7 +29,7 @@ compileKotlin {
2929
loom {
3030
launchConfigs {
3131
client {
32-
arg("--tweakClass", "cc.woverflow.onecore.tweaker.OneCoreTweaker")
32+
arg("--tweakClass", "cc.woverflow.crashpatch.hooks.ModsCheckerPlugin")
3333
property("onecore.mixin", "mixin.crashpatch.json")
3434
}
3535
}
@@ -91,7 +91,7 @@ jar {
9191

9292
manifest.attributes(
9393
"ModSide": "CLIENT",
94-
"TweakClass": "cc.woverflow.onecore.tweaker.OneCoreTweaker",
94+
"TweakClass": "cc.woverflow.crashpatch.hooks.ModsCheckerPlugin",
9595
"TweakOrder": "0",
9696
"MixinConfigs": "mixin.crashpatch.json",
9797
'ForceLoadAsMod': true
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
package cc.woverflow.crashpatch.hooks;
2+
3+
import cc.woverflow.onecore.tweaker.OneCoreTweaker;
4+
import com.google.common.collect.Lists;
5+
import com.google.gson.JsonObject;
6+
import com.google.gson.JsonParser;
7+
import com.google.gson.stream.MalformedJsonException;
8+
import net.minecraft.launchwrapper.Launch;
9+
import net.minecraft.launchwrapper.LaunchClassLoader;
10+
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
11+
import org.apache.commons.lang3.StringUtils;
12+
13+
import javax.swing.*;
14+
import java.awt.*;
15+
import java.io.File;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.lang.reflect.InvocationTargetException;
19+
import java.lang.reflect.Method;
20+
import java.util.List;
21+
import java.util.*;
22+
import java.util.stream.Collectors;
23+
import java.util.zip.ZipEntry;
24+
import java.util.zip.ZipFile;
25+
26+
public class ModsCheckerPlugin extends OneCoreTweaker {
27+
private static final JsonParser PARSER = new JsonParser();
28+
public static final HashMap<String, Triple<File, String, String>> modsMap = new HashMap<>(); //modid : file, version, name
29+
30+
@Override
31+
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
32+
File modsFolder = new File(Launch.minecraftHome, "mods");
33+
File[] modFolder = modsFolder.listFiles((dir, name) -> name.endsWith(".jar"));
34+
HashMap<String, ArrayList<Triple<File, String, String>>> dupeMap = new HashMap<>();
35+
if (modFolder != null) {
36+
for (File file : modFolder) {
37+
try {
38+
try (ZipFile mod = new ZipFile(file)) {
39+
ZipEntry entry = mod.getEntry("mcmod.info");
40+
if (entry != null) {
41+
try (InputStream inputStream = mod.getInputStream(entry)) {
42+
byte[] availableBytes = new byte[inputStream.available()];
43+
inputStream.read(availableBytes, 0, inputStream.available());
44+
JsonObject modInfo = PARSER.parse(new String(availableBytes)).getAsJsonArray().get(0).getAsJsonObject();
45+
if (!modInfo.has("modid") || !modInfo.has("version")) {
46+
continue;
47+
}
48+
49+
String modid = modInfo.get("modid").getAsString();
50+
if (modsMap.containsKey(modid)) {
51+
if (dupeMap.containsKey(modid)) {
52+
dupeMap.get(modid).add(new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid));
53+
} else {
54+
dupeMap.put(modid, Lists.newArrayList(modsMap.get(modid), new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid)));
55+
}
56+
} else {
57+
modsMap.put(modid, new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid));
58+
}
59+
}
60+
}
61+
}
62+
} catch (MalformedJsonException | IllegalStateException ignored) {
63+
} catch (Exception e) {
64+
e.printStackTrace();
65+
}
66+
}
67+
}
68+
69+
Iterator<ArrayList<Triple<File, String, String>>> iterator = dupeMap.values().iterator();
70+
71+
while (iterator.hasNext()) {
72+
try {
73+
ArrayList<Triple<File, String, String>> next = iterator.next();
74+
List<Triple<File, String, String>> blank = next.stream().sorted((a, b) -> {
75+
if (a != null && b != null) {
76+
try {
77+
int value = new DefaultArtifactVersion(substringBeforeAny(a.second, "-beta", "-alpha", "-pre", "+beta", "+alpha", "+pre")).compareTo(new DefaultArtifactVersion(substringBeforeAny(b.second, "-beta", "-alpha", "-pre", "+beta", "+alpha", "+pre")));
78+
return -value;
79+
} catch (Exception e) {
80+
e.printStackTrace();
81+
try {
82+
String[] array = {a.second, b.second};
83+
Arrays.sort(array);
84+
return array[0].equals(a.second) ? -1 : Objects.equals(a.second, b.second) ? 0 : 1;
85+
} catch (Exception ex) {
86+
ex.printStackTrace();
87+
return 0;
88+
}
89+
}
90+
}
91+
return 0;
92+
}).collect(Collectors.toList());
93+
next.clear();
94+
next.addAll(blank);
95+
ListIterator<Triple<File, String, String>> otherIterator = next.listIterator();
96+
int index = 0;
97+
while (otherIterator.hasNext()) {
98+
Triple<File, String, String> remove = otherIterator.next();
99+
++index;
100+
if (index != 1) {
101+
tryDeleting(remove.first);
102+
otherIterator.remove();
103+
}
104+
}
105+
if (next.size() <= 1) {
106+
iterator.remove();
107+
}
108+
} catch (Exception e) {
109+
e.printStackTrace();
110+
}
111+
}
112+
113+
if (modsMap.containsKey("itlt")) {
114+
tryDeleting(modsMap.get("itlt").first);
115+
}
116+
if (modsMap.containsKey("custommainmenu")) {
117+
tryDeleting(modsMap.get("custommainmenu").first);
118+
}
119+
if (!dupeMap.isEmpty()) {
120+
try {
121+
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
122+
} catch (Exception e) {
123+
e.printStackTrace();
124+
}
125+
126+
DesktopManager.open(modsFolder);
127+
JOptionPane.showMessageDialog(null, "Duplicate mods have been detected! These mods are...\n" +
128+
getStringOf(dupeMap.values()) + "\nPlease removes these mods from your mod folder, which is opened. Go to https://inv.wtf/skyclient for more info.", "Duplicate Mods Detected!", JOptionPane.ERROR_MESSAGE);
129+
try {
130+
Class<?> exitClass = Class.forName("java.lang.Shutdown");
131+
Method exit = exitClass.getDeclaredMethod("exit", int.class);
132+
exit.setAccessible(true);
133+
exit.invoke(null, 0);
134+
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
135+
e.printStackTrace();
136+
}
137+
}
138+
super.injectIntoClassLoader(classLoader);
139+
}
140+
141+
private String getStringOf(Collection<ArrayList<Triple<File, String, String>>> dupes) {
142+
StringBuilder builder = new StringBuilder();
143+
for (ArrayList<Triple<File, String, String>> list : dupes) {
144+
builder.append("\n");
145+
for (Triple<File, String, String> triple : list) {
146+
builder.append(" ").append(triple.first.getAbsolutePath());
147+
}
148+
}
149+
return builder.toString().trim();
150+
}
151+
152+
private String substringBeforeAny(String string, String... values) {
153+
String returnString = string;
154+
for (String value : values) {
155+
if (returnString.contains(value)) {
156+
returnString = StringUtils.substringBefore(returnString, value);
157+
}
158+
}
159+
return returnString;
160+
}
161+
162+
private void tryDeleting(File file) {
163+
if (!file.delete()) {
164+
if (!file.delete()) {
165+
if (!file.delete()) {
166+
file.deleteOnExit();
167+
}
168+
}
169+
}
170+
}
171+
172+
public static class Triple<A, B, C> {
173+
public A first;
174+
public B second;
175+
public C third;
176+
177+
public Triple(A a, B b, C c) {
178+
first = a;
179+
second = b;
180+
third = c;
181+
}
182+
183+
@Override
184+
public String toString() {
185+
return "Triple{" +
186+
"first=" + first +
187+
", second=" + second +
188+
", third=" + third +
189+
'}';
190+
}
191+
}
192+
193+
/**
194+
* Taken from UniversalCraft under LGPLv3
195+
* https://github.com/EssentialGG/UniversalCraft/blob/master/LICENSE
196+
*/
197+
private static class DesktopManager {
198+
private static final boolean isLinux;
199+
private static final boolean isXdg;
200+
private static boolean isKde;
201+
private static boolean isGnome;
202+
private static final boolean isMac;
203+
private static final boolean isWindows;
204+
205+
static {
206+
String osName;
207+
try {
208+
osName = System.getProperty("os.name");
209+
} catch (SecurityException ignored) {
210+
osName = null;
211+
}
212+
isLinux = osName != null && (osName.startsWith("Linux") || osName.startsWith("LINUX"));
213+
isMac = osName != null && osName.startsWith("Mac");
214+
isWindows = osName != null && osName.startsWith("Windows");
215+
if (isLinux) {
216+
String xdg = System.getenv("XDG_SESSION_ID");
217+
isXdg = xdg != null && !xdg.isEmpty();
218+
String gdm = System.getenv("GDMSESSION");
219+
if (gdm != null) {
220+
String lowercaseGDM = gdm.toLowerCase(Locale.ENGLISH);
221+
isGnome = lowercaseGDM.contains("gnome");
222+
isKde = lowercaseGDM.contains("kde");
223+
}
224+
} else {
225+
isXdg = false;
226+
isKde = false;
227+
isGnome = false;
228+
}
229+
}
230+
231+
232+
public static void open(File file) {
233+
if (!openDesktop(file)) {
234+
openSystemSpecific(file.getPath());
235+
}
236+
}
237+
238+
private static boolean openSystemSpecific(String file) {
239+
return isLinux ? (isXdg ? runCommand("xdg-open \"" + file + '"') : (isKde ? runCommand("kde-open \"" + file + '"') : (isGnome ? runCommand("gnome-open \"" + file + '"') : runCommand("kde-open \"" + file + '"') || runCommand("gnome-open \"" + file + '"')))) : (isMac ? runCommand("open \"" + file + '"') : (isWindows && runCommand("explorer \"" + file + '"')));
240+
}
241+
242+
private static boolean openDesktop(File file) {
243+
boolean worked;
244+
if (!Desktop.isDesktopSupported()) {
245+
worked = false;
246+
} else {
247+
boolean worked2;
248+
try {
249+
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
250+
return false;
251+
}
252+
253+
Desktop.getDesktop().open(file);
254+
worked2 = true;
255+
} catch (Throwable var4) {
256+
worked2 = false;
257+
}
258+
259+
worked = worked2;
260+
}
261+
262+
return worked;
263+
}
264+
265+
private static boolean runCommand(String command) {
266+
try {
267+
Process process = Runtime.getRuntime().exec(command);
268+
return process != null && process.isAlive();
269+
} catch (IOException var5) {
270+
return false;
271+
}
272+
}
273+
}
274+
}

src/main/kotlin/cc/woverflow/crashpatch/CrashPatch.kt

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package cc.woverflow.crashpatch
22

33
import cc.woverflow.crashpatch.crashes.CrashHelper
44
import cc.woverflow.crashpatch.crashes.DeobfuscatingRewritePolicy
5+
import cc.woverflow.crashpatch.hooks.ModsCheckerPlugin
56
import cc.woverflow.onecore.utils.Updater
6-
import cc.woverflow.onecore.utils.asJsonElement
77
import cc.woverflow.onecore.utils.command
8-
import com.google.gson.stream.MalformedJsonException
98
import gg.essential.api.EssentialAPI
109
import gg.essential.api.utils.Multithreading
1110
import gg.essential.universal.ChatColor
@@ -16,7 +15,6 @@ import net.minecraftforge.fml.common.event.FMLInitializationEvent
1615
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
1716
import org.apache.logging.log4j.LogManager
1817
import java.io.File
19-
import java.util.zip.ZipFile
2018

2119

2220
@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "gg.essential.api.utils.KotlinAdapter")
@@ -25,35 +23,7 @@ object CrashPatch {
2523
const val MODID = "crashpatch"
2624
const val NAME = "CrashPatch"
2725
const val VERSION = "@VERSION@"
28-
val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(modDir, "SKYCLIENT").exists() || File(Launch.minecraftHome, "mods").listFiles { _, name -> name.endsWith(".jar") }?.let { list ->
29-
list.forEach {
30-
try {
31-
ZipFile(it).use { zipFile ->
32-
val entry = zipFile.getEntry("mcmod.info")
33-
if (entry != null) {
34-
zipFile.getInputStream(entry).use { inputStream ->
35-
val availableBytes = ByteArray(inputStream.available())
36-
inputStream.read(availableBytes, 0, inputStream.available())
37-
val modInfo =
38-
String(availableBytes).asJsonElement().asJsonArray[0].asJsonObject
39-
if (!modInfo.has("modid")) {
40-
return@forEach
41-
}
42-
val modid = modInfo["modid"].asString
43-
if (modid == "skyclientcosmetics" || modid == "scc" || modid == "skyclientaddons" || modid == "skyblockclientupdater") {
44-
return@let true
45-
}
46-
}
47-
}
48-
}
49-
} catch (ignored: MalformedJsonException) {
50-
} catch (ignored: IllegalStateException) {
51-
} catch (e: Exception) {
52-
e.printStackTrace()
53-
}
54-
}
55-
return@let false
56-
} ?: false }
26+
val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(modDir, "SKYCLIENT").exists() || ModsCheckerPlugin.modsMap.keys.any { it == "skyclientcosmetics" || it == "scc" || it == "skyclientaddons" || it == "skyblockclientupdater" } }
5727
val gameDir: File by lazy(LazyThreadSafetyMode.PUBLICATION) {
5828
try {
5929
if (Launch.minecraftHome.parentFile?.name == (if (UDesktop.isMac) "minecraft" else ".minecraft")) Launch.minecraftHome.parentFile else Launch.minecraftHome

src/main/kotlin/cc/woverflow/crashpatch/gui/GuiCrashMenu.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import cc.woverflow.crashpatch.gui.components.TextButton
1111
import cc.woverflow.crashpatch.hooks.CrashReportHook
1212
import cc.woverflow.crashpatch.utils.InternetUtils
1313
import cc.woverflow.onecore.utils.browseURL
14+
import cc.woverflow.onecore.utils.sendBrandedNotification
1415
import gg.essential.elementa.ElementaVersion
1516
import gg.essential.elementa.WindowScreen
1617
import gg.essential.elementa.components.ScrollComponent
@@ -198,6 +199,7 @@ class GuiCrashMenu @JvmOverloads constructor(val report: CrashReport, private va
198199
}
199200
return@run null
200201
})
202+
sendBrandedNotification("CrashPatch", "Copied crash report to clipboard!")
201203
} constrain {
202204
x = (openCrashReport.getRight() + 5).pixels()
203205
y = CenterConstraint()

src/main/kotlin/cc/woverflow/crashpatch/gui/GuiServerDisconnectMenu.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import cc.woverflow.crashpatch.gui.components.TextButton
77
import cc.woverflow.crashpatch.logger
88
import cc.woverflow.crashpatch.utils.InternetUtils
99
import cc.woverflow.onecore.utils.browseURL
10+
import cc.woverflow.onecore.utils.sendBrandedNotification
1011
import gg.essential.elementa.ElementaVersion
1112
import gg.essential.elementa.WindowScreen
1213
import gg.essential.elementa.components.ScrollComponent
@@ -159,6 +160,7 @@ class GuiServerDisconnectMenu(private val component: IChatComponent, reason: Str
159160
}
160161
return@run null
161162
})
163+
sendBrandedNotification("CrashPatch", "Copied crash report to clipboard!")
162164
} constrain {
163165
x = (openCrashReport.getRight() + 5).pixels()
164166
y = CenterConstraint()

0 commit comments

Comments
 (0)