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
865 stars 43 forks source link

Epic: Steam Workshop Support #324

Open erri120 opened 1 year ago

erri120 commented 1 year ago

Introduction

The Steam Workshop is a central-hub for easily uploading and installing mods. It has integrated support for various games, like Darkest Dungeon, most Paradox games, Mount & Blade 2, the XCOM games, Divinity Original Sin 2, various Bethesda games, and over 2000 other titles.

The level of integration can vary from game to game. In the case of Darkest Dungeon, you can subscribe to mods in the Steam Workshop, they will be downloaded to [steamLibrary]/workshop/content/[appId] and can be enabled or disabled inside the game's internal mod configuration menu. This menu also allows the user to change the priority order of mods and seamlessly works with both, Steam Workshop mods and manually installed mods. The game also features a Steam Workshop Uploader, and modding guides recommend using it when creating mods, as it also handles localizations and other miscellaneous tasks. As such, the majority of mods for Darkest Dungeon are available on the Steam Workshop.

Wallpaper Engine, although not a game, is a great example for how well integrated the Steam Workshop can be. For creators, it has an in-build editor that can automatically upload the wallpaper to the Workshop. For users, it has an in-build gallery to quickly subscribe and unsubscribe to wallpapers. Within only a couple seconds after subscribing to an item in the gallery, Steam will download the wallpaper and the app responds by changing the current wallpaper immediately after the download has finished.

Steam Workshop in detail

The Steam Workshop is not a mod manager, instead it's a hub for downloading mods. A user can "subscribe" to, or "unsubscribe" from, an item. This is done by opening the Steam Workshop for a specific game, going to a mod page and clicking the "Subscribe" or "Unsubscribe" button.

When the user subscribes to a mod, Steam will automatically download the mod and keep it updated. Steam regularly checks for mod updates and automatically downloads new versions. Since there is no apparent mod history, users are forced to always use the latest version of a mod. When the user unsubscribes from a mod, Steam will automatically remove the downloaded files.

Additionally, users can create "Collections", which are simply a list of items from the Steam Workshop. Any user can open a public collection and use the "Subscribe to all" and "Unsubscribe to all" buttons to quickly download multiple mods at once.

Overall, the Steam Workshop leaves the implementation to the games and only offers a basic frontend for downloading mods, as well as tools for uploading mods. As described above, games can choose how they want to interact with the Workshop.

Requirements

To support the games we play, our mod manager should interact with the Steam Workshop. The level of interaction is up for debate, here are some of the potential features and their technical challenges:

View mods installed via the Steam Workshop in the Loadout

This is the bare minimum for Steam Workshop support. Even if the user can't interact with these mods, meaning they can't update or remove them from within the app, the mods should at least be visible and marked as Steam Workshop mods in the Loadout. We'd have to change parts of the Data Model to accommodate "read-only" mods and update the UI, but getting a list of every mod installed with the Steam Workshop is very straightforward and can easily be implemented in GameFinder.

Manage mods

This section is about adding, removing, updating and modifying mods.

"Adding" mods using the Steam Workshop is done by "subscribing" to items. As detailed above, this is done by going to a mod page and clicking the "Subscribe" button. Similarly, "removing" mods is done by "unsubscribing" using the "Unsubscribe" button. This is very different from the usual process of adding and removing mods. Using Nexus Mods, as an example: the user opens the browser and navigates to the site, they open a mod page and hit the "Mod Manager Download" button on a specific file they want to download. Currently, this uses nxm links to open any registered mod managers which then download the file. Since the Steam Workshop is integrated into Steam, downloading is done through Steam itself, instead of an external program. We'd be able to tell the Steam Workshop to "subscribe" to, or "unsubscribe" from, an item, but doing would move the process of downloading from the app to Steam itself.

This raises some questions: What is the user experience for adding Steam Workshop mods to the app? Should the user "subscribe" to items using the Steam interface and the app will detect when a new mod gets added, or should we have some internal UI that displays the mod and has an "add mod" button that simply notifies Steam about the new "subscription".

The user experience for removing mods is better, as they'd be able to "unsubscribe" from within the app, or using Steam. We'd have to get notified or check if a mod was "unsubscribed" using Steam instead of the app.

Updating mods is completely in the hands of Steam. They can update any "subscribed" mod at any point and our app has to check for changes. This also means we can't modify the mod files, since they will just be overwritten when Steam auto-updates the mod. We'd have to mark the mod as "read-only" and provide other ways for overwriting these files (eg: using a load order).

Technical Details

Steamworks SDK

The SDK offers methods for subscribing and unsubscribing to items, listing all subscribed items, as well as registering for callbacks when the user subscribes or unsubscribes from an item.

This SDK is unavailable for us, as the app is not a game published on Steam. Even if the app was on Steam, the SDK is limited to the current "game" only.

SteamCMD

The Steam command-line interface offers various commands for interacting with the workshop:

workshop_download_item $AppId $PublishedFileId

The CLI requires authentication in the form of a username+password combination. It's also running in another process and opaque from the outside, meaning we can't make meaningful observations using the output or what's actually going on inside. As such, this method is very unreliable and has too many technical issues.

Steam Web API

The Steam Web API has endpoints that are available to both, users and publishers. The IPublishedFileService namespaces contains endpoints specifically for using the Steam Workshop:

These three endpoints are simple HTTP APIs and return JSON. They do, however, require an API key. This is a user-generated API key, that can be obtained from https://steamcommunity.com/dev/apikey.

Here are some examples:

Getting the details of a mod **Using cURL**: ```bash curl "https://api.steampowered.com/IPublishedFileService/GetDetails/v1?key=$APIKEY&publishedfileids\[0\]=2896035307 ``` **Response**: ```json { "response": { "publishedfiledetails": [ { "result": 1, "publishedfileid": "2896035307", "creator": "76561198295501978", "creator_appid": 262060, "consumer_appid": 262060, "consumer_shortcutid": 0, "filename": "", "file_size": "364196", "preview_file_size": "237392", "preview_url": "https://steamuserimages-a.akamaihd.net/ugc/2014827585302773470/BBDDF3CB423FD1B518D7B1042C63AC85E89C784A/", "url": "", "hcontent_file": "3773690489726287141", "hcontent_preview": "2014827585302773470", "title": "No Redundant Quirks", "file_description": "\n\tAn extension of Moonslime's Less Quirks. \n\t\n- Quirks in each row are now incompatible with each other. - \n\t\n\tMan Slayer - Mankind Hater - Fear of Man - Automatonophobia - Necromania\n\t\n\tBeast Slayer - Beast Hater - Fear of Beasts - Zoophobia - Necromania\n\t\n\tUnholy Slayer - Unholy Hater - Fear of Unholy - Satanophobia - Dark Temptation - Demonomania\n\t\n\tEldritch Slayer - Eldritch Hater - Fear of Eldritch - Dark Temptation - Demonomania\n\t\t\n\tRuins Explorer - Ruins Adventurer - Ruins Tactician - Ruins Scrounger - Ruins Phobe\n\t\n\tWeald Explorer - Weald Adventurer - Weald Tactician - Weald Scrounger - Weald Phobe\n\t\n\tWarrens Explorer - Warrens Adventurer - Warrens Tactician - Warrens Scrounger - Warrens Phobe\n\t\n\tCove Explorer - Cove Adventurer - Cove Tactician - Cove Scrounger - Cove Phobe\n\t\n\tTough - Soft\n\t\n\tSkilled Gambler - Bad Gambler - Known Cheat\n\t\n\tMeditator - Unquiet Mind\n\n\tEgomania - Impostor Syndrome\n\n\tAntsy - Calm\n\t\n- Nonreligious heroes can't get God Fearing, Flagellant, Hagiomania, or Guilty Conscience -\n\n- Religious heroes can't get Witness, Faithless, Dark Temptation, Demonomania, or Paranormania -\n\n- Ranged-only heroes can't get Precise Striker, Weak Grip, Slugger, or Torn Rotator - \n\n- Melee-only heroes can't get Eagle Eye, Flawed Release, Natural Eye, Lazy Eye, Unerring, or Scattering -\n\n- This mod won't work with any mod that also touches the vanilla quirks. - \n\n\t", "time_created": 1669919587, "time_updated": 1670078051, "visibility": 0, "flags": 5632, "workshop_file": false, "workshop_accepted": false, "show_subscribe_all": false, "num_comments_public": 11, "banned": false, "ban_reason": "", "banner": "76561197960265728", "can_be_deleted": true, "app_name": "Darkest Dungeon®", "file_type": 0, "can_subscribe": true, "subscriptions": 345, "favorited": 35, "followers": 0, "lifetime_subscriptions": 586, "lifetime_favorited": 42, "lifetime_followers": 0, "lifetime_playtime": "0", "lifetime_playtime_sessions": "0", "views": 2423, "num_children": 0, "num_reports": 0, "tags": [ { "tag": "Gameplay Tweaks", "display_name": "Gameplay Tweaks" } ], "language": 0, "maybe_inappropriate_sex": false, "maybe_inappropriate_violence": false, "revision_change_number": "4", "revision": 1, "ban_text_check_result": 5 } ] } } ```
Subscribing to a mod **Using cURL**: ```bash curl -X POST "https://api.steampowered.com/IPublishedFileService/Subscribe/v1" --data-urlencode "key=$APIKEY" --data-urlencode "publishedfileid=2896035307" --data-urlencode "list_type=1" --data-urlencode "notify_client=1" ``` **Response**: ```json {"response":{}} ```
Unsubscribing from a mod **Using cURL**: ```bash curl -X POST "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1" --data-urlencode "key=$APIKEY" --data-urlencode "publishedfileid=2896035307" --data-urlencode "list_type=1" --data-urlencode "notify_client=1" ``` **Response**: ```json {"response":{}} ```

SteamPipe

This is Steam's content distribution system, and it can be used to download Workshop mods. It's used internally by SteamCMD, but can be used elsewhere. The tool steamctl is able to download Workshop items using the hcontent_file field from the API, but, like SteamCMD, it requires username+password authentication.

Summary

It's possible and easy to list installed Steam Workshop mods. "Subscribing" to, and "unsubscribing" from, such mods is only practical using the Steam Web API. This, however, requires an API key, which the user has to generate. Updating is left completely in the hands of Steam itself, so Steam Workshop mods would have to be marked as "read-only" and checked regularly for changed files.

Aragas commented 1 year ago

In case of the Steam Web API, without internet access subscription/unsubscription will not be possible, unlike NexusMods App, which will be able to delete mods offline.

With Bannerlord, we are able to manage (enable/disable) Steam Workshop mods without using the Steam SDK. We treat the workshop folder as an additional mod provider, just like the /Modules, with lower priority (Mods installed in /Modules will be used even if it's also available via Steam Workshop). So in our case, we just need both locations from NMA, but if talking about a higher level abstraction, NMA might need to expose an additional mod providers interface that will have it's own way for adding/deleting mods if that's possible by the provider

Pickysaurus commented 1 year ago

In case of the Steam Web API, without internet access subscription/unsubscription will not be possible, unlike NexusMods App, which will be able to delete mods offline.

With Bannerlord, we are able to manage (enable/disable) Steam Workshop mods without using the Steam SDK. We treat the workshop folder as an additional mod provider, just like the /Modules, with lower priority (Mods installed in /Modules will be used even if it's also available via Steam Workshop). So in our case, we just need both locations from NMA, but if talking about a higher level abstraction, NMA might need to expose an additional mod providers interface that will have it's own way for adding/deleting mods if that's possible by the provider

Presumably, you could delete the Workshop mod folder if the Steam API was not available. Not that it's an elegant option as it'll probably be restored by Steam when you're online again.

Aragas commented 1 year ago

Presumably, you could delete the Workshop mod folder if the Steam API was not available. Not that it's an elegant option as it'll probably be restored by Steam when you're online again.

Yes, but the restore operation will be an unexpected operation for the user. This could be worked around by sending the unsubscribe command to Steam once the user is online, but in case when NMA is closed and Steam is running, the mods will be restored.

basxto commented 4 months ago

Regarding SteamCMD: If steam guard is enabled it also needs the OTP code … per session.