204 lines
6.8 KiB
JavaScript
204 lines
6.8 KiB
JavaScript
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,
|
|
};
|