Skip to content

Instantly share code, notes, and snippets.

@Vocaned
Created March 13, 2026 23:49
Show Gist options
  • Select an option

  • Save Vocaned/bbdf9318fc2379b65108dae26f29dac3 to your computer and use it in GitHub Desktop.

Select an option

Save Vocaned/bbdf9318fc2379b65108dae26f29dac3 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path
from zipfile import ZipFile
game_to_nexus = {
"Morrowind": "morrowind",
"Oblivion": "oblivion",
"Fallout3": "fallout3",
"FalloutNewVegas": "newvegas",
"Skyrim": "skyrim",
"SkyrimSpecialEdition": "skyrimspecialedition",
"Fallout4": "fallout4",
"SkyrimVR": "skyrimspecialedition",
"Enderal": "enderal",
"EnderalSpecialEdition": "enderalspecialedition",
"Fallout4VR": "fallout4",
"DarkestDungeon": "darkestdungeon",
"Dishonored": "dishonored",
"Witcher": "witcher",
"Witcher3": "witcher3",
"StardewValley": "stardewvalley",
"KingdomComeDeliverance": "kingdomcomedeliverance",
"MechWarrior5Mercenaries": "mechwarrior5mercenaries",
"NoMansSky": "nomanssky",
"DragonAgeOrigins": "dragonage",
"DragonAge2": "dragonage2",
"DragonAgeInquisition": "dragonageinquisition",
"KerbalSpaceProgram": "kerbalspaceprogram",
"Cyberpunk2077": "cyberpunk2077",
"Sims4": "thesims4",
"DragonsDogma": "dragonsdogma",
"Valheim": "valheim",
"MountAndBlade2Bannerlord": "mountandblade2bannerlord",
"FinalFantasy7Remake": "finalfantasy7remake",
"BaldursGate3": "baldursgate3",
"Starfield": "starfield",
"SevenDaysToDie": "7daystodie",
"OblivionRemastered": "oblivionremastered",
"Fallout76": "fallout76",
"Fallout4London": "fallout4london",
"Warhammer40kDarktide": "warhammer40kdarktide",
"Kotor2": "kotor2",
"VtMB": "vampirebloodlines",
"KingdomComeDeliverance2": "kingdomcomedeliverance2",
"DragonsDogma2": "dragonsdogma2",
"ModdingTools": "site",
}
def main():
parser = argparse.ArgumentParser(description='Identify missing mods from a Wabbajack file.')
parser.add_argument('wabbajack_file', help='Path to the .wabbajack file')
parser.add_argument('downloads_directory', help='Directory to search for existing mod archives')
args = parser.parse_args()
if not Path(args.wabbajack_file).is_file():
print(f'Error: Wabbajack file \'{args.wabbajack_file}\' not found.', file=sys.stderr)
sys.exit(1)
if not Path(args.downloads_directory).is_dir():
print(f'Error: Directory \'{args.downloads_directory}\' not found.', file=sys.stderr)
sys.exit(1)
try:
with ZipFile(args.wabbajack_file, 'r') as wabbajack_zip:
with wabbajack_zip.open('modlist') as f:
data = json.loads(f.read())
except Exception as e:
print(f'Error reading JSON: {e}', file=sys.stderr)
sys.exit(1)
for archive in data.get('Archives', []):
name = archive.get('Name')
state = archive.get('State', {})
game_name = state.get('GameName')
mod_id = state.get('ModID')
file_id = state.get('FileID')
if mod_id and file_id and game_name and name:
game = game_to_nexus.get(game_name)
if not game:
print(f'Warning: Could not map \'{game_name}\' to a nexus game.', file=sys.stderr)
game = game_name
if not Path(args.downloads_directory, name).exists():
url = f'https://www.nexusmods.com/{game}/mods/{mod_id}?tab=files&file_id={file_id}'
print(url)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment