249 lines
7.1 KiB
Dart
249 lines
7.1 KiB
Dart
import 'dart:io';
|
|
import 'package:xml/xml.dart';
|
|
|
|
const root = r'C:/Users/Administrator/Seafile/Games-Rimworld';
|
|
const modsRoot = '$root/294100';
|
|
const configRoot = '$root/AppData/RimWorld by Ludeon Studios/Config';
|
|
const configPath = '$configRoot/ModsConfig.xml';
|
|
|
|
XmlElement findCaseInsensitive(XmlElement element, String name) {
|
|
return element.childElements.firstWhere(
|
|
(e) => e.name.local.toLowerCase() == name,
|
|
);
|
|
}
|
|
|
|
XmlElement findCaseInsensitiveDoc(XmlDocument document, String name) {
|
|
name = name.toLowerCase();
|
|
return document.childElements.firstWhere(
|
|
(e) => e.name.local.toLowerCase() == name,
|
|
);
|
|
}
|
|
|
|
class Mod {
|
|
final String name; // ModMetaData.name
|
|
final String id; // ModMetaData.packageId
|
|
final String path; // figure it out
|
|
final List<String> versions; // ModMetaData.supportedVersions
|
|
final String description; // ModMetaData.description
|
|
final List<String>
|
|
hardDependencies; // ModMetaData.modDependencies - this is a li with packageId, displayName, steamWorkshopUrl and downloadUrl
|
|
final List<String> softDependencies; // ModMetaData.loadAfter
|
|
final List<String> incompatabilities; // ModMetaData.incompatibleWith
|
|
final bool
|
|
enabled; // ConfigFile.mods.firstWhere((mod) => mod.id == id).enabled
|
|
final int size; // Count of files in the mod directory
|
|
|
|
Mod({
|
|
required this.name,
|
|
required this.id,
|
|
required this.path,
|
|
required this.versions,
|
|
required this.description,
|
|
required this.hardDependencies,
|
|
required this.softDependencies,
|
|
required this.incompatabilities,
|
|
required this.enabled,
|
|
required this.size,
|
|
});
|
|
|
|
static Mod fromDirectory(String path) {
|
|
final aboutFile = File('$path/About/About.xml');
|
|
if (!aboutFile.existsSync()) {
|
|
throw Exception('About.xml file does not exist in $aboutFile');
|
|
}
|
|
final aboutXml = XmlDocument.parse(aboutFile.readAsStringSync());
|
|
|
|
late final XmlElement metadata;
|
|
try {
|
|
metadata = findCaseInsensitiveDoc(aboutXml, 'ModMetaData');
|
|
} catch (e) {
|
|
throw Exception(
|
|
'Error: ModMetaData element is missing in About.xml ($aboutFile). Original error: $e',
|
|
);
|
|
}
|
|
|
|
late final String name;
|
|
try {
|
|
name = metadata.findElements('name').first.innerText;
|
|
} catch (e) {
|
|
throw Exception(
|
|
'Error: name element is missing in ModMetaData ($aboutFile). Original error: $e',
|
|
);
|
|
}
|
|
|
|
late final String id;
|
|
try {
|
|
id = metadata.findElements('packageId').first.innerText.toLowerCase();
|
|
} catch (e) {
|
|
throw Exception(
|
|
'Error: packageId element is missing in ModMetaData ($aboutFile). Original error: $e',
|
|
);
|
|
}
|
|
|
|
late final List<String> versions;
|
|
try {
|
|
versions =
|
|
metadata
|
|
.findElements('supportedVersions')
|
|
.first
|
|
.findElements('li')
|
|
.map((e) => e.innerText)
|
|
.toList();
|
|
} catch (e) {
|
|
throw Exception(
|
|
'Error: supportedVersions or li elements are missing in ModMetaData ($aboutFile). Original error: $e',
|
|
);
|
|
}
|
|
|
|
String description = '';
|
|
try {
|
|
description = metadata.findElements('description').first.innerText;
|
|
} catch (e) {
|
|
print('$name has no description');
|
|
}
|
|
|
|
List<String> hardDependencies = [];
|
|
try {
|
|
hardDependencies =
|
|
metadata
|
|
.findElements('modDependenciesByVersion')
|
|
.last
|
|
.findElements('li')
|
|
.map(
|
|
(e) =>
|
|
e.findElements('packageId').first.innerText.toLowerCase(),
|
|
)
|
|
.toList();
|
|
} catch (e) {
|
|
print('$name has no hard dependencies');
|
|
}
|
|
|
|
List<String> softDependencies = [];
|
|
try {
|
|
softDependencies =
|
|
metadata
|
|
.findElements('loadAfter')
|
|
.first
|
|
.findElements('li')
|
|
.map((e) => e.innerText.toLowerCase())
|
|
.toList();
|
|
} catch (e) {
|
|
print('$name has no soft dependencies');
|
|
}
|
|
|
|
List<String> incompatabilities = [];
|
|
try {
|
|
incompatabilities =
|
|
metadata
|
|
.findElements('incompatibleWith')
|
|
.first
|
|
.findElements('li')
|
|
.map((e) => e.innerText.toLowerCase())
|
|
.toList();
|
|
} catch (e) {
|
|
print('$name has no incompatabilities');
|
|
}
|
|
|
|
final size =
|
|
Directory(path)
|
|
.listSync(recursive: true)
|
|
.where(
|
|
(entity) =>
|
|
!entity.path
|
|
.split(Platform.pathSeparator)
|
|
.last
|
|
.startsWith('.'),
|
|
)
|
|
.length;
|
|
|
|
return Mod(
|
|
name: name,
|
|
id: id,
|
|
path: path,
|
|
versions: versions,
|
|
description: description,
|
|
hardDependencies: hardDependencies,
|
|
softDependencies: softDependencies,
|
|
incompatabilities: incompatabilities,
|
|
enabled: false,
|
|
size: size,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ModList {
|
|
final String path;
|
|
Map<String, Mod> mods = {};
|
|
|
|
ModList({required this.path});
|
|
|
|
Stream<Mod> load() async* {
|
|
final directory = Directory(path);
|
|
print('Loading configuration from: $path');
|
|
|
|
if (directory.existsSync()) {
|
|
final List<FileSystemEntity> entities = directory.listSync();
|
|
final List<String> modDirectories =
|
|
entities.whereType<Directory>().map((dir) => dir.path).toList();
|
|
|
|
print('Found ${modDirectories.length} mod directories:');
|
|
|
|
for (final modDir in modDirectories) {
|
|
try {
|
|
// Add a small delay to prevent UI freezing and give time for rendering
|
|
await Future.delayed(const Duration(milliseconds: 5));
|
|
|
|
final mod = Mod.fromDirectory(modDir);
|
|
mods[mod.id] = mod;
|
|
print(
|
|
'Loaded mod: ${mod.name} (ID: ${mod.id}) from directory: $modDir. '
|
|
'Size: ${mod.size}, '
|
|
'Hard Dependencies: ${mod.hardDependencies.join(', ')}, '
|
|
'Soft Dependencies: ${mod.softDependencies.join(', ')}, '
|
|
'Incompatibilities: ${mod.incompatabilities.join(', ')}',
|
|
);
|
|
|
|
yield mod;
|
|
print('Current total mods loaded: ${mods.length}');
|
|
} catch (e) {
|
|
print('Error loading mod from directory: $modDir');
|
|
print('Error: $e');
|
|
}
|
|
}
|
|
} else {
|
|
print('Mods root directory does not exist: $modsRoot');
|
|
}
|
|
}
|
|
}
|
|
|
|
class ConfigFile {
|
|
final String path;
|
|
List<Mod> mods;
|
|
|
|
ConfigFile({required this.path, this.mods = const []});
|
|
|
|
void load() {
|
|
final file = File(path);
|
|
print('Loading configuration from: $path');
|
|
|
|
final xmlString = file.readAsStringSync();
|
|
print('XML content read successfully.');
|
|
|
|
final xmlDocument = XmlDocument.parse(xmlString);
|
|
print('XML document parsed successfully.');
|
|
|
|
final modConfigData = xmlDocument.findElements("ModsConfigData").first;
|
|
print('Found ModsConfigData element.');
|
|
|
|
final modsElement = modConfigData.findElements("activeMods").first;
|
|
print('Found activeMods element.');
|
|
|
|
final mods = modsElement.findElements("li");
|
|
print('Found ${mods.length} active mods.');
|
|
|
|
for (final mod in mods) {
|
|
// print('Mod found: ${mod.innerText}');
|
|
}
|
|
}
|
|
}
|