diff --git a/lib/mod_list.dart b/lib/mod_list.dart new file mode 100644 index 0000000..9a25430 --- /dev/null +++ b/lib/mod_list.dart @@ -0,0 +1,195 @@ +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); +}