@nexusmods/vortex-api 1.0.0 → 2.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +619 -0
- package/README.md +262 -0
- package/bin/extractInfo.mjs +45 -0
- package/docs/EVENTS.md +203 -0
- package/docs/EXAMPLES.md +75 -0
- package/docs/MIGRATION.md +370 -0
- package/lib/api.d.ts +9597 -0
- package/package.json +100 -9
- package/nexusmods-vortex-api-1.0.0.tgz +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Vortex API
|
|
2
|
+
|
|
3
|
+
Type definitions for the [Vortex](https://www.nexusmods.com/about/vortex/) extension API.
|
|
4
|
+
|
|
5
|
+
This package provides auto-generated typings that extensions can use to interact with Vortex - the mod manager by Nexus Mods.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
For packaging and distributing extensions, see [Packaging extensions for Vortex](https://wiki.nexusmods.com/index.php/Packaging_extensions_for_Vortex).
|
|
10
|
+
|
|
11
|
+
For API changes between versions, see the [Changelog](CHANGELOG.md).
|
|
12
|
+
|
|
13
|
+
For a full list of events extensions can listen to or emit, see the [Events Reference](docs/EVENTS.md).
|
|
14
|
+
|
|
15
|
+
For notable open-source extensions with advanced patterns, see the [Example Extensions](docs/EXAMPLES.md).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npm install vortex-api
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`vortex-api` declares Vortex runtime packages (React, Redux, Bluebird, etc.) as `peerDependencies`. With pnpm, npm 7+, or Yarn Berry these are installed automatically - you don't need to add them to your own `devDependencies`. For details on migrating from earlier versions, see the [Migration Guide](docs/MIGRATION.md).
|
|
24
|
+
|
|
25
|
+
## Extension structure
|
|
26
|
+
|
|
27
|
+
A Vortex extension is a JavaScript module that exports an `init` function. The function receives an `IExtensionContext` object that provides access to the full API.
|
|
28
|
+
|
|
29
|
+
`info.json`
|
|
30
|
+
|
|
31
|
+
- `name` - the display name of your extension.
|
|
32
|
+
- `author` - the extension author's name.
|
|
33
|
+
- `version` - the version of your extension.
|
|
34
|
+
- `description` - a short description of what your extension does.
|
|
35
|
+
|
|
36
|
+
`index.js`
|
|
37
|
+
|
|
38
|
+
- This is the main entry point of your extension.
|
|
39
|
+
- Import Vortex API types using `import { types, util, selectors } from 'vortex-api'`
|
|
40
|
+
- Must export a `default` function (or named `init`) that receives `IExtensionContext`.
|
|
41
|
+
- Must bundle all external dependencies into the output.
|
|
42
|
+
|
|
43
|
+
## App architecture
|
|
44
|
+
|
|
45
|
+
#### Vortex is organized around a few core systems:
|
|
46
|
+
|
|
47
|
+
- **Extensions** - modular plugins that add game support, UI pages, settings, and more. Extensions interact with Vortex through the `IExtensionContext` interface.
|
|
48
|
+
- **State (Redux)** - all application state is managed through a Redux store. Extensions can register reducers and react to state changes.
|
|
49
|
+
- **Profiles** - users can create multiple mod profiles per game, each with its own set of enabled mods and configuration.
|
|
50
|
+
- **Mod Management** - handles mod installation, deployment (via symlinks or hardlinks), and conflict resolution.
|
|
51
|
+
|
|
52
|
+
#### Through `IExtensionContext`, extensions can:
|
|
53
|
+
|
|
54
|
+
- Register game support using `registerGame`.
|
|
55
|
+
- Add main pages and dialog pages using `registerMainPage` and `registerDialog`.
|
|
56
|
+
- Add mod installers using `registerInstaller` and `registerModType`.
|
|
57
|
+
- Add action buttons and toolbar items using `registerAction`.
|
|
58
|
+
- Add settings pages using `registerSettings`.
|
|
59
|
+
- Access the Redux store via `context.api.getState()` and `context.api.store`.
|
|
60
|
+
- Show notifications and dialogs via `context.api.sendNotification` and `context.api.showDialog`.
|
|
61
|
+
|
|
62
|
+
#### Registering to lifecycle events
|
|
63
|
+
|
|
64
|
+
Extensions can hook into the application lifecycle:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
function init(context: IExtensionContext) {
|
|
68
|
+
// Called when the extension is loaded.
|
|
69
|
+
// Register your features here.
|
|
70
|
+
|
|
71
|
+
context.once(() => {
|
|
72
|
+
// Called after all extensions have been loaded.
|
|
73
|
+
// Safe to interact with other extensions here.
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Game extensions
|
|
79
|
+
|
|
80
|
+
The [`extensions/games/`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games) directory in the Vortex repository contains game support extensions that serve as practical examples. They range from simple to complex:
|
|
81
|
+
|
|
82
|
+
#### Simple game support (~100 lines)
|
|
83
|
+
|
|
84
|
+
A minimal game extension registers the game and tells Vortex where to find it. Only `id`, `name`, `executable`, `requiredFiles`, and `queryModPath` are required - everything else is optional:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
function init(context: IExtensionContext) {
|
|
88
|
+
context.registerGame({
|
|
89
|
+
id: "mygame",
|
|
90
|
+
name: "My Game",
|
|
91
|
+
mergeMods: true,
|
|
92
|
+
logo: "gameart.jpg",
|
|
93
|
+
executable: () => "MyGame.exe",
|
|
94
|
+
requiredFiles: ["MyGame.exe"],
|
|
95
|
+
queryModPath: () => "Mods",
|
|
96
|
+
// Vortex will auto-discover the game across all supported stores
|
|
97
|
+
queryArgs: {
|
|
98
|
+
steam: [{ name: "My Game" }],
|
|
99
|
+
gog: [{ id: "1234567890" }],
|
|
100
|
+
xbox: [{ id: "PublisherName.MyGame" }],
|
|
101
|
+
epic: [{ id: "abc123def456" }],
|
|
102
|
+
registry: [{ id: "HKEY_LOCAL_MACHINE:Software\\MyGame:InstallPath" }],
|
|
103
|
+
},
|
|
104
|
+
// Environment variables set when launching the game executable
|
|
105
|
+
environment: {
|
|
106
|
+
SteamAPPId: "12345",
|
|
107
|
+
},
|
|
108
|
+
// Metadata used by Vortex and Nexus Mods integration
|
|
109
|
+
details: {
|
|
110
|
+
steamAppId: 12345, // numeric Steam app ID for API lookups
|
|
111
|
+
gogAppId: "1234567890", // GOG app ID
|
|
112
|
+
nexusPageId: "mygame", // the game's URL slug on nexusmods.com
|
|
113
|
+
hashFiles: [
|
|
114
|
+
// files used to identify the game version via hashing
|
|
115
|
+
"MyGame.exe",
|
|
116
|
+
path.join("Data", "Main.esm"),
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
See: [`game-skyrimse`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-skyrimse) for a full example with multi-store support, or [`game-darksouls`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-darksouls), [`game-grimrock`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-grimrock) for simpler cases.
|
|
126
|
+
|
|
127
|
+
#### Custom installers
|
|
128
|
+
|
|
129
|
+
Extensions can register installers to handle game-specific mod formats. The installer tests whether it can handle an archive, then returns file placement instructions:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
context.registerInstaller("rimworld-mod", 50, testSupported, installContent);
|
|
133
|
+
|
|
134
|
+
async function testSupported(files: string[]) {
|
|
135
|
+
const hasManifest = files.find((f) => path.basename(f) === "About.xml") !== undefined;
|
|
136
|
+
return { supported: hasManifest, requiredFiles: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function installContent(files: string[]) {
|
|
140
|
+
const instructions = files
|
|
141
|
+
.filter((f) => !f.endsWith(path.sep))
|
|
142
|
+
.map((f) => ({ type: "copy", source: f, destination: f }));
|
|
143
|
+
return { instructions };
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See: [`game-rimworld`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-rimworld), [`game-kenshi`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-kenshi), [`game-stardewvalley`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-stardewvalley), [`game-neverwinter-nights`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-neverwinter-nights) (file-extension-to-destination routing), [Cyberpunk 2077](https://github.com/E1337Kat/cyberpunk2077_ext_redux) (multi-type detection from a single archive), [Elden Ring](https://github.com/Senjay-id/eldenring-vortex-extension) (5 priority-based installers), [Ready Or Not](https://github.com/BeYkeRYkt/vortex_readyornot_extension) (type-specific test/install pairs)
|
|
148
|
+
|
|
149
|
+
#### Custom mod types
|
|
150
|
+
|
|
151
|
+
Games with multiple mod installation targets can register mod types so users can choose where files are deployed:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
context.registerModType(
|
|
155
|
+
"bg3-loose",
|
|
156
|
+
25,
|
|
157
|
+
(gameId) => gameId === "baldursgate3",
|
|
158
|
+
() => path.join(modsPath, "Loose"),
|
|
159
|
+
(instructions) => Promise.resolve(isLooseMod(instructions)),
|
|
160
|
+
{ name: "Loose Files" },
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See: [`game-baldursgate3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-baldursgate3), [`game-stardewvalley`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-stardewvalley), [`game-nomanssky`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-nomanssky) (mod type migration between deprecated and current formats), [Oblivion Remastered](https://github.com/Nexus-Mods/game-oblivionremastered) (6 mod formats with architecture-aware paths)
|
|
165
|
+
|
|
166
|
+
#### Load order management
|
|
167
|
+
|
|
168
|
+
Games with strict plugin ordering can register a load order system:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
context.registerLoadOrder({
|
|
172
|
+
gameId: GAME_ID,
|
|
173
|
+
deserializeLoadOrder: () => readCurrentOrder(),
|
|
174
|
+
serializeLoadOrder: (order) => writeOrder(order),
|
|
175
|
+
validate: (order) => checkForErrors(order),
|
|
176
|
+
usageInstructions: "Drag and drop to reorder plugins.",
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
See: [`game-baldursgate3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-baldursgate3), [`game-morrowind`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-morrowind) (load order with validation and collections support), [Bannerlord](https://github.com/BUTR/game-mount-and-blade2) (auto-sort on deploy), [Ready Or Not](https://github.com/BeYkeRYkt/vortex_readyornot_extension) (prefix-based filesystem ordering), [Starfield](https://github.com/Nexus-Mods/game-starfield) (conditional LOOT integration)
|
|
181
|
+
|
|
182
|
+
#### Supported tools
|
|
183
|
+
|
|
184
|
+
Extensions can bundle tool definitions so users can launch community tools (script extenders, body editors, modding utilities) directly from Vortex:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const tools: ITool[] = [
|
|
188
|
+
{
|
|
189
|
+
id: 'skse64',
|
|
190
|
+
name: 'Skyrim Script Extender 64',
|
|
191
|
+
executable: () => 'skse64_loader.exe',
|
|
192
|
+
requiredFiles: ['skse64_loader.exe'],
|
|
193
|
+
relative: true,
|
|
194
|
+
exclusive: true,
|
|
195
|
+
defaultPrimary: true,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
context.registerGame({ ..., supportedTools: tools });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
See: [`game-skyrimse`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-skyrimse), [`game-stardewvalley`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-stardewvalley), [`game-oblivion`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-oblivion)
|
|
203
|
+
|
|
204
|
+
#### Event hooks
|
|
205
|
+
|
|
206
|
+
Extensions can react to deployment, installation, and game mode changes:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
context.once(() => {
|
|
210
|
+
context.api.onAsync("did-deploy", async (profileId, deployment) => {
|
|
211
|
+
// Called after mods are deployed - sync load order, write config, etc.
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
context.api.events.on("gamemode-activated", (gameId: string) => {
|
|
215
|
+
// Called when the user switches game mode.
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
For a full list of available events, see the [Events Reference](docs/EVENTS.md).
|
|
221
|
+
|
|
222
|
+
See: [`game-baldursgate3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-baldursgate3), [`game-stardewvalley`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-stardewvalley), [`game-witcher3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-witcher3) (extensive event-driven pipeline), [Oblivion Remastered](https://github.com/Nexus-Mods/game-oblivionremastered) (INI merge on `will-deploy`, Lua processing on `did-deploy`), [Elden Ring](https://github.com/Senjay-id/eldenring-vortex-extension) (auto-set primary tool on deploy)
|
|
223
|
+
|
|
224
|
+
#### File merge support
|
|
225
|
+
|
|
226
|
+
Extensions can register merge functions to combine files from multiple mods (e.g. XML config files):
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
context.registerMerge(
|
|
230
|
+
(game, discovery) => canMerge(game, discovery), // test: can this game merge?
|
|
231
|
+
(filePath, mergePath) => doMerge(filePath, mergePath), // perform the merge
|
|
232
|
+
"merge-id",
|
|
233
|
+
);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
See: [`game-dragonage`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-dragonage) (XML merge for AddIns.xml), [`game-witcher3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-witcher3) (script merger tool integration), [Starfield](https://github.com/Nexus-Mods/game-starfield) (INI merge system)
|
|
237
|
+
|
|
238
|
+
#### Multi-game registration
|
|
239
|
+
|
|
240
|
+
A single extension can register multiple game variants:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
function init(context: IExtensionContext) {
|
|
244
|
+
context.registerGame({ id: 'neverwinter', name: 'Neverwinter Nights', ... });
|
|
245
|
+
context.registerGame({ id: 'neverwinteree', name: 'Neverwinter Nights: Enhanced Edition', ... });
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
See: [`game-neverwinter-nights`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-neverwinter-nights)
|
|
251
|
+
|
|
252
|
+
## Example extensions
|
|
253
|
+
|
|
254
|
+
For a curated list of notable open-source extensions with advanced patterns, see the [Example Extensions](docs/EXAMPLES.md).
|
|
255
|
+
|
|
256
|
+
## Issues and requests
|
|
257
|
+
|
|
258
|
+
For bugs and feature requests related to the API, please open an issue on the [Vortex repository](https://github.com/Nexus-Mods/Vortex/issues).
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
[GPL-3.0](LICENSE.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
|
|
6
|
+
function transformName(input) {
|
|
7
|
+
return (
|
|
8
|
+
input.charAt(0).toUpperCase() +
|
|
9
|
+
input.substr(1).replace(/-([a-z])/g, (match, m1) => " " + m1.toUpperCase())
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getExtensionName(pkgInfo) {
|
|
14
|
+
if (pkgInfo.config && pkgInfo.config.game) {
|
|
15
|
+
return `Game: ${pkgInfo.config.game}`;
|
|
16
|
+
}
|
|
17
|
+
if (pkgInfo.config && pkgInfo.config.extensionName) {
|
|
18
|
+
return pkgInfo.config.extensionName;
|
|
19
|
+
}
|
|
20
|
+
return transformName(pkgInfo.name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* function intended to be run during build of an extension,
|
|
25
|
+
* extracting details about it from its package.json
|
|
26
|
+
* @param {string} extPath path to the extension
|
|
27
|
+
* @returns {import('vortex-api').types.IExtension} an extensionInfo object
|
|
28
|
+
*/
|
|
29
|
+
function extractExtensionInfo(extPath) {
|
|
30
|
+
const pkgInfo = JSON.parse(fs.readFileSync(path.join(extPath, "package.json")).toString());
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name: getExtensionName(pkgInfo),
|
|
34
|
+
namespace: pkgInfo.config?.namespace,
|
|
35
|
+
author: pkgInfo.author,
|
|
36
|
+
version: pkgInfo.version,
|
|
37
|
+
description: pkgInfo.description,
|
|
38
|
+
issueTrackerURL: pkgInfo.config?.issueTracker,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
path.join("dist", "info.json"),
|
|
44
|
+
JSON.stringify(extractExtensionInfo(process.cwd())),
|
|
45
|
+
);
|
package/docs/EVENTS.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Vortex Events Reference
|
|
2
|
+
|
|
3
|
+
This document lists all events that extensions can listen to or emit through the Vortex API.
|
|
4
|
+
|
|
5
|
+
## Event patterns
|
|
6
|
+
|
|
7
|
+
Vortex uses two event mechanisms:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// Synchronous events - fire-and-forget
|
|
11
|
+
context.api.events.on('event-name', (args) => { ... });
|
|
12
|
+
context.api.events.emit('event-name', args);
|
|
13
|
+
|
|
14
|
+
// Async events - handlers return promises, caller can await all handlers
|
|
15
|
+
context.api.onAsync('event-name', async (args) => { ... });
|
|
16
|
+
await context.api.emitAndAwait('event-name', args);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Register event handlers inside `context.once()` to ensure all extensions are loaded first.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Game mode & profile
|
|
24
|
+
|
|
25
|
+
| Event | Args | Description |
|
|
26
|
+
| --------------------- | --------------------- | -------------------------------------- |
|
|
27
|
+
| `gamemode-activated` | `(gameId: string)` | User switched to a different game mode |
|
|
28
|
+
| `activate-game` | `(gameId: string)` | Request to activate a game |
|
|
29
|
+
| `profile-did-change` | `(profileId: string)` | Profile has been switched |
|
|
30
|
+
| `profile-will-change` | - | About to switch profiles |
|
|
31
|
+
|
|
32
|
+
## Game discovery
|
|
33
|
+
|
|
34
|
+
| Event | Args | Description |
|
|
35
|
+
| ---------------------------- | -------------------------------------------- | -------------------------------------------- |
|
|
36
|
+
| `discover-game` | `(gameId: string)` | Async. Discover a specific game installation |
|
|
37
|
+
| `discover-tools` | `(gameId: string)` | Async. Discover tools for a game |
|
|
38
|
+
| `start-discovery` | - | Start full game discovery scan |
|
|
39
|
+
| `start-quick-discovery` | `(cb?: (gameIds: string[]) => void)` | Quick discovery of known games |
|
|
40
|
+
| `cancel-discovery` | - | Cancel ongoing discovery |
|
|
41
|
+
| `refresh-game-info` | `(gameId: string, cb: (err: Error) => void)` | Refresh cached info for a game |
|
|
42
|
+
| `manually-set-game-location` | `(gameId: string, cb: (err: Error) => void)` | Manually set game install path |
|
|
43
|
+
|
|
44
|
+
## Deployment
|
|
45
|
+
|
|
46
|
+
| Event | Args | Description |
|
|
47
|
+
| -------------------- | ----------------------------------------------------------- | -------------------------------------- |
|
|
48
|
+
| `will-deploy` | `(profileId: string, deployment?: IDeploymentManifest)` | Async. Before deployment begins |
|
|
49
|
+
| `did-deploy` | `(profileId: string, deployment?: IDeploymentManifest)` | Async. After deployment completes |
|
|
50
|
+
| `will-purge` | `(profileId: string, lastDeployment?: IDeploymentManifest)` | Async. Before purge (un-deploy) begins |
|
|
51
|
+
| `did-purge` | `(profileId: string)` | Async. After purge completes |
|
|
52
|
+
| `deploy-mods` | `(profileId: string, cb?, ...)` | Request mod deployment |
|
|
53
|
+
| `deploy-single-mod` | `(gameId: string, modId: string, ...)` | Async. Deploy a single mod |
|
|
54
|
+
| `purge-mods` | `(allowFallback: boolean, cb: (err: Error) => void)` | Purge all deployed mods |
|
|
55
|
+
| `purge-mods-in-path` | `(gameId: string, modType: string, modPath: string)` | Async. Purge mods in a specific path |
|
|
56
|
+
| `await-activation` | `(cb: (err: Error) => void)` | Wait for current deployment to finish |
|
|
57
|
+
|
|
58
|
+
## Mod installation
|
|
59
|
+
|
|
60
|
+
| Event | Args | Description |
|
|
61
|
+
| ------------------------ | ------------------------------------------------------- | ------------------------------- |
|
|
62
|
+
| `will-install-mod` | `(gameId: string, archiveId: string, modId: string)` | Async. Before mod installation |
|
|
63
|
+
| `did-install-mod` | `(gameId: string, archiveId: string, modId: string)` | After mod is installed |
|
|
64
|
+
| `start-install` | `(archivePath: string, cb?: (err, id: string) => void)` | Start install from archive path |
|
|
65
|
+
| `start-install-download` | `(downloadId: string, ...)` | Start install from a download |
|
|
66
|
+
| `create-mod` | `(gameId: string, mod: IMod, cb: (err: Error) => void)` | Create a new mod entry |
|
|
67
|
+
| `mod-content-changed` | - | Mod files on disk changed |
|
|
68
|
+
| `simulate-installer` | - | Simulate mod installation |
|
|
69
|
+
|
|
70
|
+
## Mod removal
|
|
71
|
+
|
|
72
|
+
| Event | Args | Description |
|
|
73
|
+
| ------------------ | ----------------------------------------------------------------- | -------------------------------- |
|
|
74
|
+
| `will-remove-mod` | `(gameId: string, modId: string, options?: IRemoveModOptions)` | Async. Before single mod removal |
|
|
75
|
+
| `did-remove-mod` | `(gameId: string, modId: string, ...)` | Async. After single mod removal |
|
|
76
|
+
| `will-remove-mods` | `(gameId: string, modIds: string[], options?: IRemoveModOptions)` | Async. Before batch mod removal |
|
|
77
|
+
| `did-remove-mods` | `(gameId: string, removedMods: IMod[])` | Async. After batch removal |
|
|
78
|
+
| `remove-mod` | `(gameId: string, modId: string, ...)` | Request single mod removal |
|
|
79
|
+
| `remove-mods` | `(gameId: string, modIds: string[], ...)` | Request batch mod removal |
|
|
80
|
+
|
|
81
|
+
## Mod state
|
|
82
|
+
|
|
83
|
+
| Event | Args | Description |
|
|
84
|
+
| ------------------------------- | ------------------------------------ | --------------------------------------------- |
|
|
85
|
+
| `mod-enabled` | `(profileId: string, modId: string)` | A mod was enabled in a profile |
|
|
86
|
+
| `mods-enabled` | - | One or more mods were enabled |
|
|
87
|
+
| `did-enable-mods` | - | Async. Mods were enabled (load order trigger) |
|
|
88
|
+
| `recalculate-modtype-conflicts` | - | Recalculate mod type conflicts |
|
|
89
|
+
|
|
90
|
+
## Downloads
|
|
91
|
+
|
|
92
|
+
| Event | Args | Description |
|
|
93
|
+
| ------------------------- | ------------------------------------------------------------- | ---------------------------------------------------- |
|
|
94
|
+
| `start-download` | `(urls, modInfo, fileName?, cb?, ...)` | Start downloading a file |
|
|
95
|
+
| `start-download-url` | `(url: string)` | Start download from a direct URL |
|
|
96
|
+
| `pause-download` | `(downloadId, cb?)` | Pause an active download |
|
|
97
|
+
| `resume-download` | `(downloadId, cb?, options?)` | Resume a paused download |
|
|
98
|
+
| `remove-download` | `(downloadId, cb?, options?: IDownloadRemoveOptions)` | Remove a download |
|
|
99
|
+
| `did-finish-download` | `(downloadId: string, state: string)` | Download completed |
|
|
100
|
+
| `import-downloads` | `([filePath], cb: (dlIds: string[]) => void)` | Import downloads from files |
|
|
101
|
+
| `did-import-downloads` | `(dlIds: string[], cb?: (err?: Error) => void)` | Downloads were imported |
|
|
102
|
+
| `did-move-downloads` | - | Downloads folder was moved |
|
|
103
|
+
| `will-move-downloads` | - | Downloads folder is about to move |
|
|
104
|
+
| `refresh-downloads` | `(gameId: string, cb: (err) => void)` | Refresh download list |
|
|
105
|
+
| `enable-download-watch` | `(enabled: boolean)` | Enable/disable download folder watching |
|
|
106
|
+
| `set-download-games` | `(dlId: string, gameIds: string[], fromMetadata?: boolean)` | Async. Assign game(s) to a download |
|
|
107
|
+
| `filehash-calculated` | `(filePath: string, md5Hash: string, fileSize: number)` | File hash was computed |
|
|
108
|
+
| `get-download-free-slots` | `(cb: (freeSlots: number) => void)` | Query available download slots |
|
|
109
|
+
| `start-download-update` | `(source, modId, fileId?, ...)` | Async. Start download update check |
|
|
110
|
+
| `browse-for-download` | `(navUrl: string, instructions: string, skippable?: boolean)` | Async. Open browser for download. Returns `string[]` |
|
|
111
|
+
|
|
112
|
+
## Dependencies
|
|
113
|
+
|
|
114
|
+
| Event | Args | Description |
|
|
115
|
+
| --------------------------- | ------------------------------------------------------------------------- | ------------------------------------ |
|
|
116
|
+
| `install-dependencies` | `(profileId: string, gameId: string, modIds: string[], silent?: boolean)` | Install mod dependencies |
|
|
117
|
+
| `install-recommendations` | `(profileId: string, gameId: string, modIds: string[])` | Install recommended mods |
|
|
118
|
+
| `did-install-dependencies` | - | Dependencies were installed |
|
|
119
|
+
| `install-from-dependencies` | - | Async. Install from dependency list |
|
|
120
|
+
| `cancel-dependency-install` | `(modId: string)` | Async. Cancel dependency install |
|
|
121
|
+
| `reset-dependency-installs` | - | Async. Reset all dependency installs |
|
|
122
|
+
|
|
123
|
+
## Nexus Mods integration
|
|
124
|
+
|
|
125
|
+
| Event | Args | Description |
|
|
126
|
+
| ------------------------ | ------------------------------------------------------------------------- | ------------------------------------ |
|
|
127
|
+
| `nexus-download` | `(siteId, modId, fileId, ...)` | Async. Download mod from Nexus |
|
|
128
|
+
| `endorse-nexus-mod` | `(siteId: string, modId: string, version: string, endorseStatus: string)` | Async. Endorse a mod |
|
|
129
|
+
| `check-mods-version` | `(gameId, modIds?, ...)` | Async. Check mods for updates |
|
|
130
|
+
| `get-mod-files` | `(siteId, modId, ...)` | Async. Get available files for a mod |
|
|
131
|
+
| `get-latest-mods` | - | Async. Get latest mods |
|
|
132
|
+
| `get-trending-mods` | - | Async. Get trending mods |
|
|
133
|
+
| `refresh-user-info` | - | Refresh Nexus user info / session |
|
|
134
|
+
| `request-nexus-login` | `(cb)` | Prompt user to log in to Nexus |
|
|
135
|
+
| `did-login` | `(err: Error)` | Nexus login completed |
|
|
136
|
+
| `retrieve-category-list` | `(isUpdate: boolean)` | Fetch category list from Nexus |
|
|
137
|
+
| `update-categories` | `(gameId, categories, isUpdate)` | Update mod categories |
|
|
138
|
+
| `open-mod-page` | - | Open mod page on Nexus |
|
|
139
|
+
| `mod-update` | - | A mod has an update available |
|
|
140
|
+
| `mods-update` | - | Multiple mods have updates |
|
|
141
|
+
| `submit-feedback` | - | Submit user feedback |
|
|
142
|
+
| `send-metric` | - | Async. Send metric to Nexus |
|
|
143
|
+
|
|
144
|
+
## Collections
|
|
145
|
+
|
|
146
|
+
| Event | Args | Description |
|
|
147
|
+
| --------------------------------- | ---------------------------------- | ----------------------------------- |
|
|
148
|
+
| `get-nexus-collection` | `(collectionId, revisionId?, ...)` | Async. Get a Nexus collection |
|
|
149
|
+
| `get-nexus-collections` | - | Async. Get multiple collections |
|
|
150
|
+
| `get-my-collections` | - | Async. Get user's collections |
|
|
151
|
+
| `get-nexus-collection-revision` | - | Async. Get collection revision |
|
|
152
|
+
| `resolve-collection-url` | `(collectionUrl: string)` | Async. Resolve URL to collection ID |
|
|
153
|
+
| `rate-nexus-collection-revision` | - | Async. Rate a collection revision |
|
|
154
|
+
| `will-install-collection` | - | Async. Before collection install |
|
|
155
|
+
| `did-install-collection` | - | Collection was installed |
|
|
156
|
+
| `did-download-collection` | - | Collection was downloaded |
|
|
157
|
+
| `collection-postprocess-complete` | - | Collection post-processing done |
|
|
158
|
+
| `collection-mod-skipped` | - | A mod in the collection was skipped |
|
|
159
|
+
| `submit-collection` | - | Submit collection to Nexus |
|
|
160
|
+
| `update-conflicts-and-rules` | - | Async. Update collection conflicts |
|
|
161
|
+
|
|
162
|
+
## Extensions
|
|
163
|
+
|
|
164
|
+
| Event | Args | Description |
|
|
165
|
+
| --------------------------------- | ------------------------------- | ---------------------------------------- |
|
|
166
|
+
| `install-extension` | `(ext: IExtensionDownloadInfo)` | Async. Install an extension |
|
|
167
|
+
| `install-extension-from-download` | `(archiveId: string)` | Async. Install extension from download |
|
|
168
|
+
| `show-extension-page` | `(modId: number)` | Show extension manager page |
|
|
169
|
+
| `download-script-extender` | `(gameId: string)` | Async. Download script extender for game |
|
|
170
|
+
|
|
171
|
+
## Settings
|
|
172
|
+
|
|
173
|
+
| Event | Args | Description |
|
|
174
|
+
| ------------------ | ------------------------------------------------------------- | ----------------------------------- |
|
|
175
|
+
| `bake-settings` | `(gameId: string, mods: IMod[], profile: IProfile)` | Async. Apply settings to game files |
|
|
176
|
+
| `apply-settings` | `(profile: IProfile, filePath: string, parser: IniFile<any>)` | Async. Apply INI/config settings |
|
|
177
|
+
| `settings-changed` | `(path: string[])` | Application settings changed |
|
|
178
|
+
|
|
179
|
+
## UI & navigation
|
|
180
|
+
|
|
181
|
+
| Event | Args | Description |
|
|
182
|
+
| --------------------- | ----------------------------------------------------------------- | ----------------------------- |
|
|
183
|
+
| `show-main-page` | `(pageId: string)` | Navigate to a main page |
|
|
184
|
+
| `refresh-main-page` | - | Refresh current main page |
|
|
185
|
+
| `open-knowledge-base` | `(articleId: string)` | Open a knowledge base article |
|
|
186
|
+
| `hide-modal` | `(modal: string)` | Hide a modal dialog |
|
|
187
|
+
| `preview-files` | `(files: IPreviewFile[], cb?: (selection: IPreviewFile) => void)` | Show file preview dialog |
|
|
188
|
+
| `quick-launch` | - | Quick launch game/tool |
|
|
189
|
+
|
|
190
|
+
## Analytics
|
|
191
|
+
|
|
192
|
+
| Event | Args | Description |
|
|
193
|
+
| ----------------------------- | -------------------------------------------------------------------- | --------------------- |
|
|
194
|
+
| `analytics-track-event` | `(category: string, action: string, label?: string, value?: number)` | Track analytics event |
|
|
195
|
+
| `analytics-track-click-event` | `(page: string, element: string)` | Track UI click |
|
|
196
|
+
|
|
197
|
+
## Miscellaneous
|
|
198
|
+
|
|
199
|
+
| Event | Args | Description |
|
|
200
|
+
| ------------------ | ------------------------------------- | ---------------------------- |
|
|
201
|
+
| `startup` | - | Application startup complete |
|
|
202
|
+
| `report-feedback` | `(errorMessage: string)` | Report feedback/error |
|
|
203
|
+
| `trigger-test-run` | `(eventType: string, delay?: number)` | Trigger test runner |
|
package/docs/EXAMPLES.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Example Extensions
|
|
2
|
+
|
|
3
|
+
These open-source extensions are good references for advanced patterns.
|
|
4
|
+
|
|
5
|
+
### Built-in (bundled with Vortex)
|
|
6
|
+
|
|
7
|
+
- [`game-witcher3`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-witcher3) - 6 installers, script merger auto-download, XML merge support, collections, load order, custom reducers
|
|
8
|
+
- [`game-nomanssky`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-nomanssky) - mod type migration system that converts mods between deprecated and current formats
|
|
9
|
+
- [`game-morrowind`](https://github.com/Nexus-Mods/Vortex/tree/master/extensions/games/game-morrowind) - load order with validation, collections support, multi-language/locale handling
|
|
10
|
+
|
|
11
|
+
### Community
|
|
12
|
+
|
|
13
|
+
#### [Cyberpunk 2077](https://github.com/E1337Kat/cyberpunk2077_ext_redux)
|
|
14
|
+
|
|
15
|
+
A large-scale extension supporting 14+ mod frameworks (REDmod, Red4Ext, CET, Redscript, TweakXL, etc.). Notable patterns:
|
|
16
|
+
|
|
17
|
+
- **Multi-type installer** - a single archive can contain multiple mod types; the extension detects and sequences them automatically ([installer.multitype.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/installer.multitype.ts))
|
|
18
|
+
- **Layout-based detection** - infers mod type from file structure rather than metadata, with legacy format auto-conversion ([installers.layouts.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/installers.layouts.ts))
|
|
19
|
+
- **Tool hooks** - intercepts tool execution to trigger REDmod deployment before launch ([tools.redmodding.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/tools.redmodding.ts))
|
|
20
|
+
- **Dynamic feature flags** - user-configurable features backed by Redux store ([features.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/features.ts))
|
|
21
|
+
- **Load order with REDmod deployment** - ties load order serialization to the REDmod compilation pipeline ([load_order.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/load_order.ts))
|
|
22
|
+
- **Game store DLC prompts** - detects Steam/GOG/Epic and prompts users to install REDmod DLC via store-specific protocol handlers ([redmodding.ts](https://github.com/E1337Kat/cyberpunk2077_ext_redux/blob/5a069f0/src/redmodding.ts))
|
|
23
|
+
|
|
24
|
+
#### [Mount & Blade II: Bannerlord](https://github.com/BUTR/game-mount-and-blade2)
|
|
25
|
+
|
|
26
|
+
A feature-rich extension with native module integration and save game management. Notable patterns:
|
|
27
|
+
|
|
28
|
+
- **Load order with auto-sort** - advanced load order management with auto-sort on deploy, bulk enable/disable, and per-profile persistence ([loadOrder/](https://github.com/BUTR/game-mount-and-blade2/tree/c04fced/src/loadOrder))
|
|
29
|
+
- **Save game viewer** - custom UI for browsing and loading save files with BLSE support ([views/](https://github.com/BUTR/game-mount-and-blade2/tree/c04fced/src/views))
|
|
30
|
+
- **Collections support** - full integration with Vortex collections including addung custom data such as load order and mod options ([collections/](https://github.com/BUTR/game-mount-and-blade2/tree/c04fced/src/collections))
|
|
31
|
+
- **Custom settings UI** - registers a game specific settings panels with sort-on-deploy, auto-fix toggles ([settings/](https://github.com/BUTR/game-mount-and-blade2/tree/c04fced/src/settings))
|
|
32
|
+
|
|
33
|
+
#### [Ready Or Not](https://github.com/BeYkeRYkt/vortex_readyornot_extension)
|
|
34
|
+
|
|
35
|
+
Handles 6 specialized mod types (FMOD audio, movies, configs, saves, binaries, root). Notable patterns:
|
|
36
|
+
|
|
37
|
+
- **Prefix-based load ordering** - uses alphabetic prefixes (`AAA_`, `AAB_`, ...) so filesystem sorting matches user-defined priority ([index.js](https://github.com/BeYkeRYkt/vortex_readyornot_extension/blob/9448452/index.js))
|
|
38
|
+
- **Type-specific installer pairs** - each mod type has paired `test*()` and `install*()` functions for validation and routing
|
|
39
|
+
- **Corrupt load order recovery** - gracefully handles corrupted `loadOrder.json` with auto-rebuild
|
|
40
|
+
|
|
41
|
+
#### [Valheim](https://github.com/sqrrlmstr/Valheim-Extension)
|
|
42
|
+
|
|
43
|
+
Manages BepInEx framework deployment and Thunderstore integration. Notable patterns:
|
|
44
|
+
|
|
45
|
+
- **Thunderstore API integration** - fetches and downloads BepInEx packs directly from the Thunderstore API with error handling and rate limiting ([index.js](https://github.com/sqrrlmstr/Valheim-Extension/blob/e52a287/index.js))
|
|
46
|
+
- **Per-mod directory isolation** - segregates each mod into `BepInEx/plugins/[ModName]/` to prevent naming conflicts
|
|
47
|
+
- **Timestamp-based conflict resolution** - compares file modification times to prevent unintended overwrites
|
|
48
|
+
|
|
49
|
+
#### [Elden Ring](https://github.com/Senjay-id/eldenring-vortex-extension)
|
|
50
|
+
|
|
51
|
+
Supports ModEngine 2 and seamless co-op mods. Notable patterns:
|
|
52
|
+
|
|
53
|
+
- **Framework auto-download** - `prepareForModding` downloads required mod framework dependencies during initial setup ([src/index.ts](https://github.com/Senjay-id/eldenring-vortex-extension/blob/47631fc/src/index.ts))
|
|
54
|
+
- **Auto-set primary tool** - `onDidDeploy` hook automatically sets ModEngine 2 as the primary tool when mods are deployed
|
|
55
|
+
- **Multi-installer routing** - 5 priority-based installers route mods to the correct handler based on type (co-op, mod loaders, DLLs, full ModEngine 2 mods)
|
|
56
|
+
|
|
57
|
+
### Nexus-maintained
|
|
58
|
+
|
|
59
|
+
#### [Starfield](https://github.com/Nexus-Mods/game-starfield)
|
|
60
|
+
|
|
61
|
+
Supports Steam and Xbox Game Pass with platform-aware deployment. Notable patterns:
|
|
62
|
+
|
|
63
|
+
- **Directory junction strategy** - creates junctions between the game's Data folder and Documents path for unified mod deployment across platforms ([setup.ts](https://github.com/Nexus-Mods/game-starfield/blob/30ea5cb/src/setup.ts))
|
|
64
|
+
- **INI merge system** - merges ASI mod configurations into a unified INI file during deployment ([iniMerge.ts](https://github.com/Nexus-Mods/game-starfield/blob/30ea5cb/src/merges/iniMerge.ts))
|
|
65
|
+
- **Conditional LOOT integration** - load order with LOOT auto-sort when available, fallback to manual drag-and-drop ([StarFieldLoadOrder.tsx](https://github.com/Nexus-Mods/game-starfield/blob/30ea5cb/src/loadOrder/StarFieldLoadOrder.tsx))
|
|
66
|
+
- **Platform-specific installers** - separate installers for SFSE (Steam) and ASI loader (Xbox) ([installers/](https://github.com/Nexus-Mods/game-starfield/tree/30ea5cb/src/installers))
|
|
67
|
+
|
|
68
|
+
#### [Oblivion Remastered](https://github.com/Nexus-Mods/game-oblivionremastered)
|
|
69
|
+
|
|
70
|
+
Manages 6 mod formats for a UE5-based Gamebryo remaster. Notable patterns:
|
|
71
|
+
|
|
72
|
+
- **UE4SS auto-download** - automatically downloads and configures UE4SS framework from GitHub releases with version checking ([downloader.ts](https://github.com/Nexus-Mods/game-oblivionremastered/blob/489f6b9/src/downloader.ts))
|
|
73
|
+
- **Stop pattern validation** - regex-based file hierarchy validation to prevent invalid mod structures ([stopPatterns.ts](https://github.com/Nexus-Mods/game-oblivionremastered/blob/489f6b9/src/stopPatterns.ts))
|
|
74
|
+
- **Architecture-aware binaries** - handles Win64 vs WinGDK paths for Steam and Xbox builds ([modTypes.ts](https://github.com/Nexus-Mods/game-oblivionremastered/blob/489f6b9/src/modTypes.ts))
|
|
75
|
+
- **Deployment event pipeline** - INI merging on `will-deploy`, Lua mod processing on `did-deploy`, settings bake on load order change ([eventHandlers.ts](https://github.com/Nexus-Mods/game-oblivionremastered/blob/489f6b9/src/eventHandlers.ts))
|