This commit is contained in:
2026-08-05 13:41:47 +02:00
commit f6170b535f
4 changed files with 221 additions and 0 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+203
View File
@@ -0,0 +1,203 @@
const path = require('path');
const { fs, log, util } = require('vortex-api');
const GAME_ID = 'retrorewindvideostoresimulator';
const STEAM_APP_ID = '3552140';
// all relative to gamePath == <steamapps>/common/RetroRewind
const REL_PAKS = path.join('RetroRewind', 'Content', 'Paks', '~mods');
const REL_LOGICMODS = path.join('RetroRewind', 'Content', 'Paks', 'LogicMods');
const REL_UE4SS = path.join('RetroRewind', 'Binaries', 'Win64', 'ue4ss', 'Mods');
const MODTYPE_LOGICMODS = 'retrorewind-logicmods';
const MODTYPE_UE4SS = 'retrorewind-ue4ss';
const PAK_EXTS = ['.pak', '.ucas', '.utoc', '.sig'];
// archive paths come from Vortex with the host separator, normalise to '/'
function norm(filePath) {
return filePath.split(/[\\/]/).join('/');
}
function isDirectory(filePath) {
return filePath.endsWith(path.sep) || filePath.endsWith('/');
}
let _api;
// under a wine/proton-hosted Vortex (steamtinkerlaunch) there is no Steam client
// in the prefix, so the store lookup throws ENOENT on libraryfolders.vdf.
// Swallow it - Vortex then keeps the manually browsed/stored discovery path.
function findGame() {
return util.GameStoreHelper.findByAppId([STEAM_APP_ID])
.then(game => game.gamePath)
.catch(err => {
log('info', 'Retro Rewind: steam store lookup unavailable, using manual discovery',
{ error: err.message });
return undefined;
});
}
// the game object handed to modType.getPath has no usable path while the game is
// still being discovered/managed, so read the discovery from state instead
function discoveryPath(game) {
if (util.getSafe(game, ['path'], undefined) !== undefined) {
return game.path;
}
return util.getSafe(_api.store.getState(),
['settings', 'gameMode', 'discovered', GAME_ID, 'path'], undefined);
}
function modTypePath(relPath) {
return (game) => {
const gamePath = discoveryPath(game);
if (gamePath === undefined) {
log('debug', 'Retro Rewind not discovered yet, modtype path unresolved', relPath);
return undefined;
}
return path.join(gamePath, relPath);
};
}
function prepareForModding(discovery) {
return Promise.all([REL_PAKS, REL_LOGICMODS, REL_UE4SS]
.map(rel => fs.ensureDirWritableAsync(path.join(discovery.path, rel))));
}
// ---------------------------------------------------------------- UE4SS mods
// layout: <ModName>/Scripts/main.lua or <ModName>/dlls/main.dll
const UE4SS_MARKER = /(^|\/)(scripts\/main\.lua|dlls\/main\.dll)$/i;
function testUe4ssMod(files, gameId) {
const supported = (gameId === GAME_ID)
&& files.some(file => UE4SS_MARKER.test(norm(file)));
return Promise.resolve({ supported, requiredFiles: [] });
}
function installUe4ssMod(files, destinationPath) {
const marker = files.find(file => UE4SS_MARKER.test(norm(file)));
// strip "Scripts/main.lua" (2 segments) to get the mod root inside the archive
const markerSegments = norm(marker).split('/');
const rootSegments = markerSegments.slice(0, markerSegments.length - 2);
const rootPath = rootSegments.join('/');
// mod name: the folder holding Scripts/, else the staging folder name
const modName = rootSegments.length > 0
? rootSegments[rootSegments.length - 1]
: path.basename(destinationPath).replace(/\.installing$/, '');
const filtered = files.filter(file => !isDirectory(file)
&& (rootPath === '' || norm(file).startsWith(rootPath + '/')));
const instructions = filtered.map(file => ({
type: 'copy',
source: file,
destination: path.join(modName,
rootPath === '' ? norm(file) : norm(file).substr(rootPath.length + 1)),
}));
// UE4SS 3.x activates a mod folder that contains enabled.txt - no mods.txt edit,
// and the marker disappears again when Vortex purges the mod
instructions.push({
type: 'generatefile',
data: Buffer.from(''),
destination: path.join(modName, 'enabled.txt'),
});
instructions.push({ type: 'setmodtype', value: MODTYPE_UE4SS });
log('info', 'installing Retro Rewind UE4SS mod', { modName, files: filtered.length });
return Promise.resolve({ instructions });
}
// ------------------------------------------------------------ blueprint mods
// blueprint (LogicMods) paks ship inside a folder called LogicMods
const LOGICMODS_MARKER = /(^|\/)logicmods\//i;
function testLogicMod(files, gameId) {
const supported = (gameId === GAME_ID)
&& files.some(file => LOGICMODS_MARKER.test(norm(file))
&& PAK_EXTS.includes(path.extname(file).toLowerCase()));
return Promise.resolve({ supported, requiredFiles: [] });
}
function installLogicMod(files) {
const instructions = files
.filter(file => !isDirectory(file)
&& PAK_EXTS.includes(path.extname(file).toLowerCase()))
.map(file => ({
type: 'copy',
source: file,
destination: path.basename(file),
}));
instructions.push({ type: 'setmodtype', value: MODTYPE_LOGICMODS });
return Promise.resolve({ instructions });
}
// ------------------------------------------------------------------ pak mods
function testPakMod(files, gameId) {
const supported = (gameId === GAME_ID)
&& files.some(file => path.extname(file).toLowerCase() === '.pak');
return Promise.resolve({ supported, requiredFiles: [] });
}
// flatten into ~mods - archives commonly wrap the pak in one or more folders
function installPakMod(files) {
const instructions = files
.filter(file => !isDirectory(file)
&& PAK_EXTS.includes(path.extname(file).toLowerCase()))
.map(file => ({
type: 'copy',
source: file,
destination: path.basename(file),
}));
return Promise.resolve({ instructions });
}
function main(context) {
_api = context.api;
context.registerGame({
id: GAME_ID,
name: 'Retro Rewind - Video Store Simulator',
mergeMods: true,
queryPath: findGame,
queryModPath: () => REL_PAKS,
logo: 'gameart.jpg',
executable: () => 'RetroRewind.exe',
requiredFiles: ['RetroRewind.exe'],
setup: prepareForModding,
supportedTools: [],
environment: {
SteamAPPId: STEAM_APP_ID,
},
details: {
steamAppId: parseInt(STEAM_APP_ID, 10),
},
});
context.registerModType(MODTYPE_UE4SS, 25,
gameId => gameId === GAME_ID,
modTypePath(REL_UE4SS),
() => Promise.resolve(false),
{ name: 'UE4SS Mod' });
context.registerModType(MODTYPE_LOGICMODS, 25,
gameId => gameId === GAME_ID,
modTypePath(REL_LOGICMODS),
() => Promise.resolve(false),
{ name: 'Blueprint Mod (LogicMods)' });
// lower number == higher priority, so UE4SS wins over the generic pak installer
context.registerInstaller('retrorewind-ue4ss', 25, testUe4ssMod, installUe4ssMod);
context.registerInstaller('retrorewind-logicmods', 26, testLogicMod, installLogicMod);
context.registerInstaller('retrorewind-pak', 27, testPakMod, installPakMod);
return true;
}
module.exports = {
default: main,
};
+7
View File
@@ -0,0 +1,7 @@
{
"name": "game-retro-rewind",
"author": "Interstellarcodes",
"version": "1.1.2",
"description": "Vortex support for Retro Rewind - Video Store Simulator (pak, LogicMods and UE4SS mods)",
"url": "https://www.nexusmods.com/retrorewindvideostoresimulator/mods/66"
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "game-retro-rewind",
"version": "1.1.2",
"description": "Vortex support for Retro Rewind - Video Store Simulator",
"main": "index.js",
"author": "Interstellarcodes",
"license": "MIT",
"dependencies": {
"vortex-api": "^1.0.0"
}
}