From 40175eae820382d9f0b43d3ae16a0c0c216685df Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 16 May 2026 11:58:27 +0100 Subject: [PATCH 1/5] Enable ESL support in Fallout 4 VR Fixes #18789 Adds parity with Skyrim VR. --- extensions/games/game-fallout4vr/package.json | 2 +- extensions/games/game-fallout4vr/src/index.js | 82 ++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/extensions/games/game-fallout4vr/package.json b/extensions/games/game-fallout4vr/package.json index ddf695e194..6b00b6d6e9 100644 --- a/extensions/games/game-fallout4vr/package.json +++ b/extensions/games/game-fallout4vr/package.json @@ -1,6 +1,6 @@ { "name": "game-fallout4vr", - "version": "1.0.2", + "version": "1.0.3", "description": "Support for the VR variant of Fallout 4", "scripts": { "_assets": "copyfiles -u 1 -f ./assets/* dist", diff --git a/extensions/games/game-fallout4vr/src/index.js b/extensions/games/game-fallout4vr/src/index.js index 97e4e5b3fb..7db76a7ae4 100644 --- a/extensions/games/game-fallout4vr/src/index.js +++ b/extensions/games/game-fallout4vr/src/index.js @@ -4,6 +4,10 @@ const { getFileVersion } = require("exe-version"); const { util } = require("vortex-api"); const winapi = require("winapi-bindings"); +const GAME_ID = "fallout4vr"; +const ESL_ENABLER_LIB = "Daytripper4.dll"; +const ESL_NOTIF_ID = "fallout4vr-esl-enabler-notif"; + /* Ignore the Meshes\AnimTextData\AnimationOffsets\PersistantSubgraphInfoAndOffsetData.txt file as a conflict. It's present in a lot of weapon mods but doesn't matter if it's overwritten. @@ -34,7 +38,7 @@ function getGameVersion(gamePath, exePath) { return fileVersion + "-VR"; } -let tools = [ +const tools = [ { id: "FO4VREdit", name: "FO4VREdit", @@ -51,9 +55,77 @@ let tools = [ }, ]; +function isESLSupported(api) { + const state = api.getState(); + const profileId = selectors.lastActiveProfileForGame(state, GAME_ID); + const discovery = selectors.discoveryByGame(state, GAME_ID); + if (discovery?.store === "xbox") { + return false; + } + const modState = util.getSafe(state, ["persistent", "profiles", profileId, "modState"], {}); + const isEnabled = (modId) => util.getSafe(modState, [modId, "enabled"], false); + const mods = util.getSafe(state, ["persistent", "mods", GAME_ID], {}); + const hasESLEnabler = Object.keys(mods).some( + (modId) => isEnabled(modId) && mods[modId]?.attributes?.eslEnabler === true, + ); + if (hasESLEnabler) { + api.dismissNotification(ESL_NOTIF_ID); + } + return hasESLEnabler; +} + +function testEslEnabler(files, gameId) { + const isFallout4VR = gameId === GAME_ID; + const isESLEnabler = files.some((file) => file.toLowerCase().endsWith(ESL_ENABLER_LIB)); + return Promise.resolve({ + supported: isFallout4VR && isESLEnabler, + requiredFiles: [], + }); +} + +function installEslEnabler(files, destinationPath) { + const filtered = files.filter((file) => path.extname(file) !== ""); + const instructions = filtered.map((file) => { + const segments = file.split(path.sep); + segments.splice(0, 1, "Data"); + return { + type: "copy", + source: file, + destination: segments.join(path.sep), + }; + }); + + // Remove this once the mod type conflict issue is resolved + instructions.push({ type: "setmodtype", value: "dinput" }); + instructions.push({ type: "attribute", key: "eslEnabler", value: true }); + + return Promise.resolve({ instructions }); +} + +function prepare(api, discovery) { + if (isESLSupported(api)) { + return Promise.resolve(); + } + + api.sendNotification({ + id: ESL_NOTIF_ID, + type: "info", + title: "ESL Support", + message: + "Fallout 4 VR requires a mod to enable ESL support. Mod must be installed through Vortex for ESL support to work.", + actions: [ + { + title: "Download", + action: () => + util.opn("https://www.nexusmods.com/fallout4/mods/91141?tab=files").catch(() => {}), + }, + ], + }); +} + function main(context) { context.registerGame({ - id: "fallout4vr", + id: GAME_ID, name: "Fallout 4 VR", mergeMods: true, queryPath: findGame, @@ -62,6 +134,7 @@ function main(context) { logo: "gameart.jpg", executable: () => "Fallout4VR.exe", getGameVersion, + setup: (discovery) => prepare(context.api, discovery), requiredFiles: ["Fallout4VR.exe"], environment: { SteamAPPId: "611660", @@ -69,11 +142,16 @@ function main(context) { details: { steamAppId: 611660, compatibleDownloads: ["fallout4"], + supportsESL: () => isESLSupported(context.api), ignoreConflicts: IGNORED_FILES, nexusPageId: "fallout4", }, }); + context.registerInstaller("fallout4vr-esl-enabler", 10, testEslEnabler, installEslEnabler); + + context.registerTest("fallout4vr-esl-support-missing", "did-deploy", () => {}); + return true; } From 57812b9941313cbda7633df819d95f452ecaf744 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 16 May 2026 12:00:09 +0100 Subject: [PATCH 2/5] Removes test This is probably the better way, but will copy Skyrim VR for simplicity until Health Checks are easier to create. --- extensions/games/game-fallout4vr/src/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/games/game-fallout4vr/src/index.js b/extensions/games/game-fallout4vr/src/index.js index 7db76a7ae4..e64839db0c 100644 --- a/extensions/games/game-fallout4vr/src/index.js +++ b/extensions/games/game-fallout4vr/src/index.js @@ -149,9 +149,6 @@ function main(context) { }); context.registerInstaller("fallout4vr-esl-enabler", 10, testEslEnabler, installEslEnabler); - - context.registerTest("fallout4vr-esl-support-missing", "did-deploy", () => {}); - return true; } From 60d0e9cd3be3f00ae71ca7af8c8b98b2b02707d2 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 18 May 2026 12:46:29 +0100 Subject: [PATCH 3/5] Import selectors from vortex-api in index.js --- extensions/games/game-fallout4vr/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/games/game-fallout4vr/src/index.js b/extensions/games/game-fallout4vr/src/index.js index e64839db0c..6c6d71bf56 100644 --- a/extensions/games/game-fallout4vr/src/index.js +++ b/extensions/games/game-fallout4vr/src/index.js @@ -1,7 +1,7 @@ const Promise = require("bluebird"); const path = require("path"); const { getFileVersion } = require("exe-version"); -const { util } = require("vortex-api"); +const { selectors, util } = require("vortex-api"); const winapi = require("winapi-bindings"); const GAME_ID = "fallout4vr"; From 95035343e53d1237a0ae9ef1db1aab6443850b58 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 20 May 2026 08:59:17 +0100 Subject: [PATCH 4/5] Update vortex-api import to use @nexusmods --- extensions/games/game-fallout4vr/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/games/game-fallout4vr/src/index.js b/extensions/games/game-fallout4vr/src/index.js index 6c6d71bf56..03f829cf9a 100644 --- a/extensions/games/game-fallout4vr/src/index.js +++ b/extensions/games/game-fallout4vr/src/index.js @@ -1,7 +1,7 @@ const Promise = require("bluebird"); const path = require("path"); const { getFileVersion } = require("exe-version"); -const { selectors, util } = require("vortex-api"); +const { selectors, util } = require("@nexusmods/vortex-api"); const winapi = require("winapi-bindings"); const GAME_ID = "fallout4vr"; From 63fc8024b473dd94f008a131e5362e42248d3da4 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 22 May 2026 17:43:25 +0100 Subject: [PATCH 5/5] Added the additional events Nagev requested --- extensions/games/game-fallout4vr/src/index.js | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/extensions/games/game-fallout4vr/src/index.js b/extensions/games/game-fallout4vr/src/index.js index 03f829cf9a..b7de44c3d3 100644 --- a/extensions/games/game-fallout4vr/src/index.js +++ b/extensions/games/game-fallout4vr/src/index.js @@ -123,6 +123,11 @@ function prepare(api, discovery) { }); } +const sortAndResolve = (api) => { + api.events.emit("autosort-plugins", false); + return Promise.resolve(); +}; + function main(context) { context.registerGame({ id: GAME_ID, @@ -149,6 +154,48 @@ function main(context) { }); context.registerInstaller("fallout4vr-esl-enabler", 10, testEslEnabler, installEslEnabler); + + context.once(() => { + context.api.events.on("gamemode-activated", (gameId) => { + if (gameId !== GAME_ID) { + context.api.dismissNotification(ESL_NOTIF_ID); + } + }); + context.api.onAsync("did-deploy", (profileId, newDeployment) => { + const state = context.api.getState(); + const profile = selectors.profileById(state, profileId); + if (profile?.gameId !== GAME_ID) { + return Promise.resolve(); + } + const discovery = selectors.discoveryByGame(state, GAME_ID); + if (!discovery?.path || discovery?.store === "xbox") { + // Fallout 4 VR is currently not on Xbox, but it may be one day! + return Promise.resolve(); + } + + const deployedFiles = newDeployment[""]; + const modESLEnabler = deployedFiles.find((file) => + file.relPath.toLowerCase().endsWith(ESL_ENABLER_LIB.toLowerCase()), + ); + if (modESLEnabler === undefined) { + return sortAndResolve(context.api); + } + + const mods = util.getSafe(state, ["persistent", "mods", GAME_ID], {}); + const mod = Object.values(mods).find((mod) => mod.installationPath === modESLEnabler.source); + if (mod === undefined || mod.attributes.eslEnabler === true) { + return sortAndResolve(context.api); + } + + const modAttributes = { + ...mod.attributes, + eslEnabler: true, + }; + context.api.store.dispatch(actions.setModAttributes(GAME_ID, mod.id, modAttributes)); + return sortAndResolve(context.api); + }); + }); + return true; }