Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
import os import zipfile import re from datetime import datetime from packaging import version # pip install packaging import platform import shutil # -------------------------- # Terminal Colors # -------------------------- class Colors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' ORANGE = '\033[33m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' def color_print(msg, color): print(f"{color}{msg}{Colors.ENDC}") # -------------------------- # Utilities # -------------------------- def timestamp(): return datetime.now().strftime("%d-%m-%Y (%I-%M%p)") def get_desktop_path(): system = platform.system() if system == "Windows": return os.path.join(os.path.expanduser("~"), "Desktop") elif system in ["Linux", "Darwin"]: # Linux or Mac return os.path.join(os.path.expanduser("~"), "Desktop") else: return os.getcwd() def get_downloads_path(): system = platform.system() if system == "Windows": return os.path.join(os.path.expanduser("~"), "Downloads") else: return get_desktop_path() # -------------------------- # Get User Inputs # -------------------------- print("Welcome to the Minecraft Mod Scanner!") # Recursive Search Option print("Do you want a recursive search? 1=Yes 2=No") recursive_input = input("> ") recursive = recursive_input.strip() == "1" input("Press ENTER to confirm and continue...") # Game version game_version = input("Enter your Minecraft game version (e.g., 1.21.1): ").strip() # Loader choice print("What loader are you using? 1=NEOForge 2=Forge 3=Fabric") loader_choice = input("> ").strip() loader_choice = loader_choice if loader_choice in ["1","2","3"] else "1" # Root folder root_folder = os.getcwd() # Output root output_root = os.path.join(get_downloads_path(), "Duplicates Found") os.makedirs(output_root, exist_ok=True) # -------------------------- # File scanning # -------------------------- mods_info = {} # mod_name -> list of dict {filename, path, mc_version, mod_version, loader} for dirpath, dirnames, filenames in os.walk(root_folder): for file in filenames: if file.lower().endswith(".jar"): file_path = os.path.join(dirpath, file) mc_ver, mod_ver, mod_name, loader = "Unknown", "Unknown", os.path.splitext(file)[0], "Unknown" # Attempt to read metadata inside the jar try: with zipfile.ZipFile(file_path, 'r') as jar: # Check mods.toml (NeoForge / Forge) if "META-INF/mods.toml" in jar.namelist(): content = jar.read("META-INF/mods.toml").decode(errors='ignore') name_match = re.search(r'name\s*=\s*"(.*?)"', content) mc_match = re.search(r'minecraft\s*=\s*"(.*?)"', content) ver_match = re.search(r'version\s*=\s*"(.*?)"', content) if name_match: mod_name = name_match.group(1) if mc_match: mc_ver = mc_match.group(1) if ver_match: mod_ver = ver_match.group(1) loader = "NEOForge" # Optionally check fabric.mod.json here if needed except Exception as e: pass # Store info mods_info.setdefault(mod_name.lower(), []).append({ "filename": file, "path": file_path, "mc_version": mc_ver, "mod_version": mod_ver, "loader": loader }) if not recursive: break # Don't recurse # -------------------------- # Game version & loader mismatch # -------------------------- game_mismatch_folder = os.path.join(output_root, "Game Mismatch Files") os.makedirs(game_mismatch_folder, exist_ok=True) for mod_name, entries in mods_info.items(): for e in entries: if e['mc_version'] != game_version or (loader_choice=="1" and e['loader']!="NEOForge") \ or (loader_choice=="2" and e['loader']!="Forge") or (loader_choice=="3" and e['loader']!="Fabric"): dest = os.path.join(game_mismatch_folder, e['filename']) shutil.copy2(e['path'], dest) # copy, don't move color_print(f"[Game Mismatch] {e['filename']} -> {game_mismatch_folder}", Colors.WARNING) # -------------------------- # Metadata Missing # -------------------------- metadata_missing_folder = os.path.join(output_root, "Metadata Missing") os.makedirs(metadata_missing_folder, exist_ok=True) metadata_missing_file = os.path.join(metadata_missing_folder, f"Metadata Missing Mods - {timestamp()}.txt") with open(metadata_missing_file, 'w', encoding='utf-8') as f: for mod_name, entries in mods_info.items(): for e in entries: if e['mc_version']=="Unknown" or e['mod_version']=="Unknown": f.write(e['filename']+"\n") color_print(f"Metadata missing mods saved to {metadata_missing_file}", Colors.ORANGE) # -------------------------- # Duplicates & Multi-version # -------------------------- multi_version_folder = os.path.join(output_root, "Detected Multi-Version") os.makedirs(multi_version_folder, exist_ok=True) multi_version_file = os.path.join(multi_version_folder, f"Detection List - {timestamp()}.txt") with open(multi_version_file, 'w', encoding='utf-8') as f: for mod_name, entries in mods_info.items(): if len(entries)>1: f.write(f"{mod_name}:\n") for e in entries: f.write(f" - {e['filename']} [MC:{e['mc_version']}, MOD:{e['mod_version']}]\n") color_print(f"Multi-version mods saved to {multi_version_file}", Colors.ORANGE) # -------------------------- # NeoForge dependency conflicts # -------------------------- def parse_version_range(range_str): match = re.match(r'\[([\d\.]+),?\)?', range_str) if match: return match.group(1) return None def check_neoforge_conflicts(mods_info): conflicts = [] for mod_name, entries in mods_info.items(): for e in entries: try: with zipfile.ZipFile(e['path'], 'r') as jar: if "META-INF/mods.toml" in jar.namelist(): content = jar.read("META-INF/mods.toml").decode(errors='ignore') dep_blocks = re.findall(r'\[\[dependencies\..*?\]\](.*?)\n\n', content, re.DOTALL) for dep_block in dep_blocks: dep_id_match = re.search(r'modId\s*=\s*"(.*?)"', dep_block) dep_ver_match = re.search(r'versionRange\s*=\s*"(.*?)"', dep_block) if dep_id_match and dep_ver_match: dep_id = dep_id_match.group(1).lower() dep_ver_range = dep_ver_match.group(1) min_version = parse_version_range(dep_ver_range) installed = mods_info.get(dep_id, []) if installed: satisfied = False for ins in installed: try: if version.parse(ins['mod_version']) >= version.parse(min_version): satisfied = True break except: continue if not satisfied: conflicts.append({ "mod": e['filename'], "dependency": dep_id, "required": dep_ver_range, "installed": [i['mod_version'] for i in installed] }) else: conflicts.append({ "mod": e['filename'], "dependency": dep_id, "required": dep_ver_range, "installed": [] }) except: continue return conflicts neo_conflicts = check_neoforge_conflicts(mods_info) if neo_conflicts: conflicts_folder = os.path.join(output_root, "Conflicts") os.makedirs(conflicts_folder, exist_ok=True) path_conf = os.path.join(conflicts_folder, f"Conflicts - {timestamp()}.txt") with open(path_conf, 'w', encoding='utf-8') as f: for c in neo_conflicts: f.write(f"{c['mod']} requires {c['dependency']} {c['required']}, installed versions: {c['installed']}\n") color_print(f"NeoForge dependency conflicts logged to {path_conf}", Colors.ORANGE) # -------------------------- # Terminal output capture # -------------------------- terminal_folder = os.path.join(output_root, "Terminal Output") os.makedirs(terminal_folder, exist_ok=True) terminal_file = os.path.join(terminal_folder, f"Terminal Output - {timestamp()}.txt") with open(terminal_file, 'w', encoding='utf-8') as f: for mod_name, entries in mods_info.items(): f.write(f"{mod_name}:\n") for e in entries: f.write(f" - {e['filename']} [MC:{e['mc_version']}, MOD:{e['mod_version']}]\n") color_print(f"Full terminal output saved to {terminal_file}", Colors.OKBLUE) # -------------------------- # Keep terminal open with confirmations # -------------------------- while True: color_print("Do you want to close the terminal? 1=Yes 2=No", Colors.BOLD) ans = input("> ") if ans.strip()=="1": color_print("Press ENTER twice to exit...", Colors.BOLD) input() input() break elif ans.strip()=="2": color_print("Terminal will remain open.", Colors.OKGREEN) break
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)