Nexus-Mods / NexusMods.App

Home of the development of the Nexus Mods App
https://nexus-mods.github.io/NexusMods.App/
GNU General Public License v3.0
1.1k stars 52 forks source link

Handle "invalid" directory structure #325

Closed erri120 closed 9 months ago

erri120 commented 1 year ago

Introduction

Some mods can have an "invalid" directory structure. Maybe it's a Skyrim mod and the path to the plugin file inside the archive is Data\MyMod.esp instead of MyMod.esp. If such a mod would be installed into the Data folder, the final output path is going to be Data\Data\MyMod.esp. This simple case can be handled automatically, because it's very easy to identify (the top-level directory of the archive only contains a single directory named Data) and very easy to fix (move all files from \Data\* to \*).

Some Darkest Dungeon mods, especially older ones on Nexus Mods, also have this issue. However, it's a bit more complex (see docs):

Before:
.
├── flagellant_E
├── flagellant_F

After:
Female Flagellant
├── dlc
    └── 580100_crimson_court
        └── features
            └── flagellant
                └── heroes
                    └── flagellant
                        └── flagellant_E
                        └── flagellant_F

The Female Flagellant (SFW) mod is an example where the downloaded archive only contains two folders flagellant_E and flagellant_F. The description of the mod doesn't even provide an installation guide, so new users that are unfamiliar with Darkest Dungeon might just blindly install these folders to the game root directory.

Solutions

The correct directory would be dlc/580100_crimson_court/features/flagellant/heroes/flagellant. Every "hero" has their own directory containing the skins, these paths are known by experienced users but not by everyone. Detecting this specific case is harder than with the Skyrim Data folder, the app would have to parse the folder names, which all follow the same format: [WHATEVER][CLASSNAME]_[A-Z]. Using the extracted class name, the app could create the correct directory structure and move the files.

Instead of trying to fix the directory structure automatically, we could instead validate the directory structure and let the user fix it themselves. This is the approach MO2 takes, and allows plugins to implement a "data checker" which reports if the archive is actually valid (example).

Implementing this manual approach would require new UI and changes to the installation process. Currently, mod installation is handled by the LoadoutManager, which analyzes the archives and selects the IModInstaller with the highest priority. Implementations of IModInstaller can use the GetPriority method to return various levels of priority. The implementation for these methods already do "validation". The Stardew Valley SMAPI mod installer will return Priority.Highest for archives that contain a SMAPI manifest. If the archive doesn't contain a manifest, or if the current game isn't even Stardew Valley, the method returns Priority.None.

From an API design perspective, we already have a kind of "data checker" for every mod installer in the GetPriority method. However, we only have semantics for saying "I can't install this archive" and not "I can't install this archive because these paths are broken".

Summary

Handling "invalid" archives that can be fixed automatically or manually is something we need to consider, especially for games without good "native mod support". There are scenarios where identifying and fixing an issue can be done automatically without hindering the user experience or producing "false positives", while others are more complex and could be done manually. A combination of automatic and manual fixing is also possible: identify that the archive has an invalid structure, propose a fix, but let the user decide how to fix this.

Al12rs commented 1 year ago

To better explain how MO2 handles this:

MO2 installer plugins have a static priority property defined by the plugin author. These values are arbitrary but purposefully left with large gaps between each installer to allow additions in the middle.

The installation manger iterates over the installer plugins in priority order. The first plugin in priority order that reports to be able to correctly handle the archive gets the job and all the following ones are skipped.

MO2 has two default installer plugins: InstallerQuick (priority 50) and InstallerManual (priority 0). Installer quick has a higher priority but can easily be superseded by other special purpose plugins such as FOMOD (110), while other installers might be under it like BAIN (40) or Bundle(10), while the Manual installer has the lowest priority (0), so is only ever used as the last resort.

Installer Quick is special because it makes use of the game extension to try to get the information necessary for installation.

Game extensions in Mo2 can implement the ModDataChecker feature: documentation

As the documentation reports, there are two methods:

This way, through installer quick, and ModDataChecker, each game extension can define how to handle most of the installation cases where the matter is simply putting the files in the correct location (not user option installers like FOMOD). The fix() method allows automatic handling of most cases, without any need for user input.

In case all the installers are unable to handle the archive (dataLooksValid returned Invalid for example in installer quick), then the Manual installer always handles the archive and offers the user a UI to define the final folder structure that the mod should have. The Manual installer also makes use of dataLooksValid to check whether the new structure defined manually by the user appears to be valid or not, and reports this to the user through the UI (with option to ignore and install anyways).

ModDataChecker is also queried from the main application to indicate whether installed mod contents look valid or not. Mods that don't pass the check get a warning icon that can be manually dismissed by the user.

So the highlights here are:

The static priority basically assumes that when a new installer needs to be added, the author will look at the other existing installers and select a priority that allows his installer to be selected when it is suitable. This system worked pretty well for MO2.

I advocate towards offering a manual installation option, as not being able to install a mod can be very frustrating, but the UI can be tricky since users can easily be confused, especially if they also don't know how to properly fix the structure themselves. But this way, the users can look online and quickly learn how to correctly fix it without making the process very tedious for users that know how to fix it without issues.

Another good feature would be allowing users of any installer to preview the final installation folder structure to inspect whether it is what they want or not before the installation is actually performed (so avoiding having to wait for file extraction and recompression), with an option that allows to perform a manual installation instead. This could also be useful for debugging the installer plugins.

Sewer56 commented 9 months ago

@Nexus-Mods/nexusmods-app-developers

Quick question everyone.

Between:

Should we consider this issue as 'solved'?

Al12rs commented 9 months ago

I'm happy to close this, I created a new issue for showing if a mod contains invalid files in the modlist ui: #940