12 Commits
Author SHA1 Message Date
Oliver Hamlet 1bf45e5d46 Add a script to check URLs that appear in a given masterlist
The script looks in message content, message subs and locations. If a
URL appears in the given plugins index file(s), it's assumed to be
valid.

The checks aren't perfect: some links look like they work, but the
actual page they go to doesn't have any relevant data on it.

I've hardcoded a bunch of hostnames that have gone defunct, usually
because the sites are gone, but some redirect to unrelated content.

There are also a few sites that respond to requests with 403s, but not
when I access them in my web browser.

Those sites (the defunct and the 403ing) are skipped, because there's no
point checking them, but the fact they're skipped is logged.

The data/urls.csv file was generated by running:

uv run -- scripts/check_urls.py -i mlox/mlox_base.txt.yaml -i mlox/mlox_base.danae.txt.yaml -i mlox/mlox_base_legacy.txt.yaml -i mlox/mlox_user.txt.yaml  -p data/manual_mod_plugins.csv -p data/mmh_fliggerty_plugins.csv -p data/nexus_mods_morrowind_plugins.csv

Then making some manual edits, running again with "-u data/urls.tsv -w" added, and that loop repeated several times.
2026-01-16 09:21:58 +00:00
Oliver Hamlet 2e3dad60aa Add an example mlox rules file and converted metadata
The input file is from <https://raw.githubusercontent.com/mlox/mlox/bd5b39e4704d4574e2a7be3d0a598a686a646214/data/mlox_base.txt>.

The mlox_base.txt.yaml file was generated by running:

uv run --with libloot@../libloot/target/wheels/libloot-0.28.4-cp314-cp314-win_amd64.whl --python python -- import_mlox.py -p mmh_fliggerty_plugins.csv -p nexus_mods_morrowind_plugins.csv -p manual_mod_plugins.csv

The script logs no errors, and the only warnings logged are related to
the substitution of known filenames in place of filename patterns.

This also introduces a hack to workaround the only piece of input data
that can't be represented by LOOT in any way. The version predicate:

[VER<1.5 Nevena's Twin Lamps & Slave Hunters *.esp]

is the only one that uses a filename pattern that does not include
"<VER>", which makes it unrepresentable in LOOT's condition syntax.
However, the pattern matches the following known filenames:

Nevena's Twin Lamps & Slave Hunters 1.0.esp
Nevena's Twin Lamps & Slave Hunters 1.2.esp
Nevena's Twin Lamps & Slave Hunters 1.5.esp

The 1.0 plugin is only available from the Morrowind Modding History
archived by the Wayback Machine, but the other two appear in
mlox_base.txt, and the 1.5 plugin is also available from Nexus
Mods. Since there are no other matching filenames that contain a version
string, it seems safe to replace the "*" in the pattern with "<VER>".
2026-01-16 00:36:32 +00:00
Oliver Hamlet 85989897b2 Add TSVs for manually-downloaded Morrowind Rebirth files
Plus a script to help generate the index TSV file. The filenames in that
index won't necessarily all match someone else's downloads, because some
were deduplicated by Windows, so their names depend on their download order.

This covers some filename patterns that were not previously matched.
2026-01-16 00:36:31 +00:00
Oliver Hamlet a6054761ab Add support for adding names from plugin indexes to location metadata 2026-01-16 00:36:31 +00:00
Oliver Hamlet f5f1f07f04 Add scripts to download and read Morrowind Modding History mods
The script takes the CSV export of the index of mods that were hosted on
Morrowind Modding History and Great House Fliggerty before they went
down, as downloaded from <https://modlist.altervista.org/mmh/>. That CSV
is included in this commit, and results in 61 GB of downloaded files.

This commit also includes a script that reads the index CSV and
downloads directory and outputs a TSV file of all the plugin files in
the downloaded files, looking inside archives. The output TSV file is
also included in this commit.

Both scripts include a few hacks to workaround issues affecting a small
minority of mods:

- The mod IDs 2770, 2771 and 2772 are three separate uploads but form
  the three parts of a multipart 7z archive, so their downloaded files
  are named to allow the archive to be read by 7-zip.
- There are a few downloads that are just bare plugin files: these are
  written into zip files so that the plugin filenames can be preserved,
  since the download file name needs to be the mod ID.
- Some download URLs use tinyurl: these have a hardcoded mapping to the
  URLs that tinyurl points to, in case the tinyurl URLs stop working.
- Nexus Mods URLs are skipped as they're already covered by the
  fetch_nexus_plugins_index.py script.

The download script also handles the many cases where mod IDs point to
downloads that are no longer available, by skipping a mod entirely if
its ID appears in a hardcoded whitelist of known invalid downloads.
2026-01-16 00:36:31 +00:00
Oliver Hamlet 00573d87c5 Set plugin location metadata using TSV plugins index data 2026-01-14 20:00:56 +00:00
Oliver Hamlet 6b6c91af58 Support matching filename patterns against TSV file content
This allows patterns to be matched against plugin filenames from mods
hosted on Nexus Mods and fetched using the fetch_nexus_plugins_index.py
script.
2026-01-14 20:00:56 +00:00
Oliver Hamlet ad689769a7 Add a script to get the plugin names of mods hosted on Nexus Mods
This uses Nexus Mods' API to retrieve plugin filenames for the mod
uploads that are hosted there, and write them to a file as
tab-separated values.

The output TSV file is included in this commit. The source data is from
mid-October 2025.
2026-01-14 20:00:56 +00:00
Oliver Hamlet f95e35ff83 Write YAML using libloot 2026-01-13 22:28:22 +00:00
Oliver Hamlet c47113d197 Reduce unnecessary Markdown special character escapes
This reduces my test data output YAML by ~ 34 KB or ~ 1.5%.
2026-01-13 22:28:22 +00:00
Oliver Hamlet 95a5658141 Deduplicate messages, files, conditions and constraints in YAML output
Intern the values to force PyYAML to use anchors and aliases instead of
duplicating objects.

The effect on my test data output is:

No interning:         5559 KB, 94924 lines
+ Files:              3922 KB, 78568 lines
+ messages:           3808 KB, 77227 lines
+ file details:       2689 KB, 64467 lines
+ file conditions:    2268 KB, 64467 lines
+ file constraints:   2265 KB, 64467 lines
+ message contents:   2179 KB, 63451 lines
+ message conditions: 2179 KB, 63451 lines

That gives a 61% reduction in output size overall.
2026-01-13 22:28:22 +00:00
Oliver Hamlet 50c87c52d9 Add script to convert mlox rules to LOOT metadata
The script parses the mlox rules into an abstract representation. There
are two other parsers that I know of:

- mlox's is also written in Python, but mixes together parsing and
  evaluation, so isn't suitable for this use case
- plox's seems like a better starting point, but isn't directly usable
  in its current form

This script doesn't attempt to match either parser's behaviour exactly
(e.g. mlox treats whitespace as optional in more places), but instead
targets the syntax that is actually in use in the rules files present in
the https://github.com/mlox/mlox and
https://github.com/DanaePlays/mlox-rules repositories.

While the parser can handle everything in those files, not everything
supported by mlox is supported by LOOT, and the remaining differences
are intentional. See the comment at the top of the script for more
details on the differences and how they're handled.
2026-01-13 22:28:21 +00:00