import 'dart:io'; import 'package:rimworld_modman/logger.dart'; import 'package:rimworld_modman/mod.dart'; import 'package:xml/xml.dart'; class ModList { final String configPath; final String modsPath; Map mods = {}; Set activeMods = {}; Set availableMods = {}; ModList({required this.configPath, required this.modsPath}); Stream loadAvailable() async* { final logger = Logger.instance; final stopwatch = Stopwatch()..start(); final directory = Directory(modsPath); if (!directory.existsSync()) { logger.error('Error: Mods root directory does not exist: $modsPath'); return; } final List entities = directory.listSync(); final List modDirectories = entities.whereType().map((dir) => dir.path).toList(); logger.info( 'Found ${modDirectories.length} mod directories (${stopwatch.elapsedMilliseconds}ms)', ); for (final modDir in modDirectories) { try { final modStart = stopwatch.elapsedMilliseconds; // Check if this directory contains a valid mod final aboutFile = File('$modDir/About/About.xml'); if (!aboutFile.existsSync()) { logger.warning('No About.xml found in directory: $modDir'); continue; } final mod = Mod.fromDirectory(modDir); logger.info('Loaded mod from directory: ${mod.name} (ID: ${mod.id})'); if (mods.containsKey(mod.id)) { logger.warning('Mod $mod.id already exists in mods list, overwriting'); final existingMod = mods[mod.id]!; mods[mod.id] = Mod( name: mod.name, id: mod.id, path: mod.path, versions: mod.versions, description: mod.description, hardDependencies: mod.hardDependencies, loadAfter: mod.loadAfter, loadBefore: mod.loadBefore, incompatabilities: mod.incompatabilities, enabled: activeMods.contains( mod.id, ), // Set enabled based on config size: mod.size, isBaseGame: existingMod.isBaseGame, isExpansion: existingMod.isExpansion, ); logger.info('Updated existing mod: ${mod.name} (ID: ${mod.id})'); } else { mods[mod.id] = Mod( name: mod.name, id: mod.id, path: mod.path, versions: mod.versions, description: mod.description, hardDependencies: mod.hardDependencies, loadAfter: mod.loadAfter, loadBefore: mod.loadBefore, incompatabilities: mod.incompatabilities, enabled: activeMods.contains( mod.id, ), // Set enabled based on config size: mod.size, isBaseGame: mod.isBaseGame, isExpansion: mod.isExpansion, ); logger.info('Added new mod: ${mod.name} (ID: ${mod.id})'); } final modTime = stopwatch.elapsedMilliseconds - modStart; logger.info( 'Loaded mod from directory: ${mod.name} (ID: ${mod.id}) in $modTime ms', ); } catch (e) { logger.error('Error loading mod from directory: $modDir'); logger.error('Error: $e'); } } } Stream loadActive() async* { final logger = Logger.instance; final file = File(configPath); logger.info('Loading configuration from: $configPath'); try { final xmlString = file.readAsStringSync(); logger.info('XML content read successfully.'); final xmlDocument = XmlDocument.parse(xmlString); logger.info('XML document parsed successfully.'); final modConfigData = xmlDocument.findElements("ModsConfigData").first; logger.info('Found ModsConfigData element.'); final modsElement = modConfigData.findElements("activeMods").first; logger.info('Found activeMods element.'); final modElements = modsElement.findElements("li"); logger.info('Found ${modElements.length} active mods.'); // Get the list of known expansions final knownExpansionsElement = modConfigData.findElements("knownExpansions").firstOrNull; final knownExpansionIds = knownExpansionsElement != null ? knownExpansionsElement .findElements("li") .map((e) => e.innerText.toLowerCase()) .toList() : []; logger.info('Found ${knownExpansionIds.length} known expansions.'); // Clear and recreate the mods list for (final modElement in modElements) { final modId = modElement.innerText.toLowerCase(); // Check if this is a special Ludeon mod final isBaseGame = modId == 'ludeon.rimworld'; final isExpansion = !isBaseGame && modId.startsWith('ludeon.rimworld.') && knownExpansionIds.contains(modId); activeMods.add(modId); final mod = Mod( name: isBaseGame ? "RimWorld" : isExpansion ? "RimWorld ${_expansionNameFromId(modId)}" : modId, id: modId, path: '', versions: [], description: isBaseGame ? "RimWorld base game" : isExpansion ? "RimWorld expansion" : "", hardDependencies: [], loadAfter: isExpansion ? ['ludeon.rimworld'] : [], loadBefore: [], incompatabilities: [], enabled: true, size: 0, isBaseGame: isBaseGame, isExpansion: isExpansion, ); if (mods.containsKey(modId)) { logger.warning('Mod $modId already exists in mods list, overwriting'); } mods[modId] = mod; yield mod; } logger.info('Loaded ${modElements.length} mods from config file.'); } catch (e) { logger.error('Error loading configuration file: $e'); throw Exception('Failed to load config file: $e'); } } } String _expansionNameFromId(String id) { final parts = id.split('.'); if (parts.length < 3) return id; final expansionPart = parts[2]; return expansionPart.substring(0, 1).toUpperCase() + expansionPart.substring(1); }