Skip to content

Commit

Permalink
dupe mod checker
Browse files Browse the repository at this point in the history
  • Loading branch information
Wyvest committed Apr 24, 2022
1 parent 88fbbb2 commit bce75d4
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 35 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
id "net.kyori.blossom" version "1.3.0"
}

version = "1.3.7"
version = "1.3.8"
group = "cc.woverflow"
archivesBaseName = "CrashPatch"

Expand All @@ -29,7 +29,7 @@ compileKotlin {
loom {
launchConfigs {
client {
arg("--tweakClass", "cc.woverflow.onecore.tweaker.OneCoreTweaker")
arg("--tweakClass", "cc.woverflow.crashpatch.hooks.ModsCheckerPlugin")
property("onecore.mixin", "mixin.crashpatch.json")
}
}
Expand Down Expand Up @@ -91,7 +91,7 @@ jar {

manifest.attributes(
"ModSide": "CLIENT",
"TweakClass": "cc.woverflow.onecore.tweaker.OneCoreTweaker",
"TweakClass": "cc.woverflow.crashpatch.hooks.ModsCheckerPlugin",
"TweakOrder": "0",
"MixinConfigs": "mixin.crashpatch.json",
'ForceLoadAsMod': true
Expand Down
274 changes: 274 additions & 0 deletions src/main/java/cc/woverflow/crashpatch/hooks/ModsCheckerPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
package cc.woverflow.crashpatch.hooks;

import cc.woverflow.onecore.tweaker.OneCoreTweaker;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.MalformedJsonException;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class ModsCheckerPlugin extends OneCoreTweaker {
private static final JsonParser PARSER = new JsonParser();
public static final HashMap<String, Triple<File, String, String>> modsMap = new HashMap<>(); //modid : file, version, name

@Override
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
File modsFolder = new File(Launch.minecraftHome, "mods");
File[] modFolder = modsFolder.listFiles((dir, name) -> name.endsWith(".jar"));
HashMap<String, ArrayList<Triple<File, String, String>>> dupeMap = new HashMap<>();
if (modFolder != null) {
for (File file : modFolder) {
try {
try (ZipFile mod = new ZipFile(file)) {
ZipEntry entry = mod.getEntry("mcmod.info");
if (entry != null) {
try (InputStream inputStream = mod.getInputStream(entry)) {
byte[] availableBytes = new byte[inputStream.available()];
inputStream.read(availableBytes, 0, inputStream.available());
JsonObject modInfo = PARSER.parse(new String(availableBytes)).getAsJsonArray().get(0).getAsJsonObject();
if (!modInfo.has("modid") || !modInfo.has("version")) {
continue;
}

String modid = modInfo.get("modid").getAsString();
if (modsMap.containsKey(modid)) {
if (dupeMap.containsKey(modid)) {
dupeMap.get(modid).add(new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid));
} else {
dupeMap.put(modid, Lists.newArrayList(modsMap.get(modid), new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid)));
}
} else {
modsMap.put(modid, new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid));
}
}
}
}
} catch (MalformedJsonException | IllegalStateException ignored) {
} catch (Exception e) {
e.printStackTrace();
}
}
}

Iterator<ArrayList<Triple<File, String, String>>> iterator = dupeMap.values().iterator();

while (iterator.hasNext()) {
try {
ArrayList<Triple<File, String, String>> next = iterator.next();
List<Triple<File, String, String>> blank = next.stream().sorted((a, b) -> {
if (a != null && b != null) {
try {
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")));
return -value;
} catch (Exception e) {
e.printStackTrace();
try {
String[] array = {a.second, b.second};
Arrays.sort(array);
return array[0].equals(a.second) ? -1 : Objects.equals(a.second, b.second) ? 0 : 1;
} catch (Exception ex) {
ex.printStackTrace();
return 0;
}
}
}
return 0;
}).collect(Collectors.toList());
next.clear();
next.addAll(blank);
ListIterator<Triple<File, String, String>> otherIterator = next.listIterator();
int index = 0;
while (otherIterator.hasNext()) {
Triple<File, String, String> remove = otherIterator.next();
++index;
if (index != 1) {
tryDeleting(remove.first);
otherIterator.remove();
}
}
if (next.size() <= 1) {
iterator.remove();
}
} catch (Exception e) {
e.printStackTrace();
}
}

if (modsMap.containsKey("itlt")) {
tryDeleting(modsMap.get("itlt").first);
}
if (modsMap.containsKey("custommainmenu")) {
tryDeleting(modsMap.get("custommainmenu").first);
}
if (!dupeMap.isEmpty()) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}

DesktopManager.open(modsFolder);
JOptionPane.showMessageDialog(null, "Duplicate mods have been detected! These mods are...\n" +
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);
try {
Class<?> exitClass = Class.forName("java.lang.Shutdown");
Method exit = exitClass.getDeclaredMethod("exit", int.class);
exit.setAccessible(true);
exit.invoke(null, 0);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
super.injectIntoClassLoader(classLoader);
}

private String getStringOf(Collection<ArrayList<Triple<File, String, String>>> dupes) {
StringBuilder builder = new StringBuilder();
for (ArrayList<Triple<File, String, String>> list : dupes) {
builder.append("\n");
for (Triple<File, String, String> triple : list) {
builder.append(" ").append(triple.first.getAbsolutePath());
}
}
return builder.toString().trim();
}

private String substringBeforeAny(String string, String... values) {
String returnString = string;
for (String value : values) {
if (returnString.contains(value)) {
returnString = StringUtils.substringBefore(returnString, value);
}
}
return returnString;
}

private void tryDeleting(File file) {
if (!file.delete()) {
if (!file.delete()) {
if (!file.delete()) {
file.deleteOnExit();
}
}
}
}

public static class Triple<A, B, C> {
public A first;
public B second;
public C third;

public Triple(A a, B b, C c) {
first = a;
second = b;
third = c;
}

@Override
public String toString() {
return "Triple{" +
"first=" + first +
", second=" + second +
", third=" + third +
'}';
}
}

/**
* Taken from UniversalCraft under LGPLv3
* https://github.com/EssentialGG/UniversalCraft/blob/master/LICENSE
*/
private static class DesktopManager {
private static final boolean isLinux;
private static final boolean isXdg;
private static boolean isKde;
private static boolean isGnome;
private static final boolean isMac;
private static final boolean isWindows;

static {
String osName;
try {
osName = System.getProperty("os.name");
} catch (SecurityException ignored) {
osName = null;
}
isLinux = osName != null && (osName.startsWith("Linux") || osName.startsWith("LINUX"));
isMac = osName != null && osName.startsWith("Mac");
isWindows = osName != null && osName.startsWith("Windows");
if (isLinux) {
String xdg = System.getenv("XDG_SESSION_ID");
isXdg = xdg != null && !xdg.isEmpty();
String gdm = System.getenv("GDMSESSION");
if (gdm != null) {
String lowercaseGDM = gdm.toLowerCase(Locale.ENGLISH);
isGnome = lowercaseGDM.contains("gnome");
isKde = lowercaseGDM.contains("kde");
}
} else {
isXdg = false;
isKde = false;
isGnome = false;
}
}


public static void open(File file) {
if (!openDesktop(file)) {
openSystemSpecific(file.getPath());
}
}

private static boolean openSystemSpecific(String file) {
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 + '"')));
}

private static boolean openDesktop(File file) {
boolean worked;
if (!Desktop.isDesktopSupported()) {
worked = false;
} else {
boolean worked2;
try {
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
return false;
}

Desktop.getDesktop().open(file);
worked2 = true;
} catch (Throwable var4) {
worked2 = false;
}

worked = worked2;
}

return worked;
}

private static boolean runCommand(String command) {
try {
Process process = Runtime.getRuntime().exec(command);
return process != null && process.isAlive();
} catch (IOException var5) {
return false;
}
}
}
}
34 changes: 2 additions & 32 deletions src/main/kotlin/cc/woverflow/crashpatch/CrashPatch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package cc.woverflow.crashpatch

import cc.woverflow.crashpatch.crashes.CrashHelper
import cc.woverflow.crashpatch.crashes.DeobfuscatingRewritePolicy
import cc.woverflow.crashpatch.hooks.ModsCheckerPlugin
import cc.woverflow.onecore.utils.Updater
import cc.woverflow.onecore.utils.asJsonElement
import cc.woverflow.onecore.utils.command
import com.google.gson.stream.MalformedJsonException
import gg.essential.api.EssentialAPI
import gg.essential.api.utils.Multithreading
import gg.essential.universal.ChatColor
Expand All @@ -16,7 +15,6 @@ import net.minecraftforge.fml.common.event.FMLInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import org.apache.logging.log4j.LogManager
import java.io.File
import java.util.zip.ZipFile


@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "gg.essential.api.utils.KotlinAdapter")
Expand All @@ -25,35 +23,7 @@ object CrashPatch {
const val MODID = "crashpatch"
const val NAME = "CrashPatch"
const val VERSION = "@VERSION@"
val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(modDir, "SKYCLIENT").exists() || File(Launch.minecraftHome, "mods").listFiles { _, name -> name.endsWith(".jar") }?.let { list ->
list.forEach {
try {
ZipFile(it).use { zipFile ->
val entry = zipFile.getEntry("mcmod.info")
if (entry != null) {
zipFile.getInputStream(entry).use { inputStream ->
val availableBytes = ByteArray(inputStream.available())
inputStream.read(availableBytes, 0, inputStream.available())
val modInfo =
String(availableBytes).asJsonElement().asJsonArray[0].asJsonObject
if (!modInfo.has("modid")) {
return@forEach
}
val modid = modInfo["modid"].asString
if (modid == "skyclientcosmetics" || modid == "scc" || modid == "skyclientaddons" || modid == "skyblockclientupdater") {
return@let true
}
}
}
}
} catch (ignored: MalformedJsonException) {
} catch (ignored: IllegalStateException) {
} catch (e: Exception) {
e.printStackTrace()
}
}
return@let false
} ?: false }
val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(modDir, "SKYCLIENT").exists() || ModsCheckerPlugin.modsMap.keys.any { it == "skyclientcosmetics" || it == "scc" || it == "skyclientaddons" || it == "skyblockclientupdater" } }
val gameDir: File by lazy(LazyThreadSafetyMode.PUBLICATION) {
try {
if (Launch.minecraftHome.parentFile?.name == (if (UDesktop.isMac) "minecraft" else ".minecraft")) Launch.minecraftHome.parentFile else Launch.minecraftHome
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/cc/woverflow/crashpatch/gui/GuiCrashMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import cc.woverflow.crashpatch.gui.components.TextButton
import cc.woverflow.crashpatch.hooks.CrashReportHook
import cc.woverflow.crashpatch.utils.InternetUtils
import cc.woverflow.onecore.utils.browseURL
import cc.woverflow.onecore.utils.sendBrandedNotification
import gg.essential.elementa.ElementaVersion
import gg.essential.elementa.WindowScreen
import gg.essential.elementa.components.ScrollComponent
Expand Down Expand Up @@ -198,6 +199,7 @@ class GuiCrashMenu @JvmOverloads constructor(val report: CrashReport, private va
}
return@run null
})
sendBrandedNotification("CrashPatch", "Copied crash report to clipboard!")
} constrain {
x = (openCrashReport.getRight() + 5).pixels()
y = CenterConstraint()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import cc.woverflow.crashpatch.gui.components.TextButton
import cc.woverflow.crashpatch.logger
import cc.woverflow.crashpatch.utils.InternetUtils
import cc.woverflow.onecore.utils.browseURL
import cc.woverflow.onecore.utils.sendBrandedNotification
import gg.essential.elementa.ElementaVersion
import gg.essential.elementa.WindowScreen
import gg.essential.elementa.components.ScrollComponent
Expand Down Expand Up @@ -159,6 +160,7 @@ class GuiServerDisconnectMenu(private val component: IChatComponent, reason: Str
}
return@run null
})
sendBrandedNotification("CrashPatch", "Copied crash report to clipboard!")
} constrain {
x = (openCrashReport.getRight() + 5).pixels()
y = CenterConstraint()
Expand Down

0 comments on commit bce75d4

Please sign in to comment.