mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-09-02 15:00:39 +00:00
257 lines
7.7 KiB
JavaScript
Vendored
257 lines
7.7 KiB
JavaScript
Vendored
import { TRAINER_LAUNCH_REQUEST_EXPORT_KEY } from './constants.js';
|
|
import {
|
|
findExportedConstructor,
|
|
findInstanceInContainerGraph,
|
|
formatError,
|
|
isRecord,
|
|
} from './runtime.js';
|
|
|
|
export function hasMissingOptionalServices(state) {
|
|
return (
|
|
!state.gameLifecycleService ||
|
|
!state.trainerVisibilityService ||
|
|
!state.unavailableTitlesService
|
|
);
|
|
}
|
|
|
|
export function hasUnresolvedServices(state) {
|
|
return (
|
|
hasMissingOptionalServices(state) ||
|
|
!state.trainerApiService ||
|
|
!state.trainerService ||
|
|
!state.trainerLaunchRequestCtor
|
|
);
|
|
}
|
|
|
|
const OPTIONAL_SERVICE_SPECS = [
|
|
{
|
|
stateKey: 'unavailableTitlesService',
|
|
methods: [
|
|
'getUnavailableTitle',
|
|
'searchUnavailableTitles',
|
|
'getUnavailableTitlesByCorrelationIds',
|
|
],
|
|
label: 'Unavailable titles service',
|
|
},
|
|
{
|
|
stateKey: 'gameLifecycleService',
|
|
methods: ['onGameLaunched', 'onGameEnded', 'launch'],
|
|
label: 'Game lifecycle service',
|
|
},
|
|
{
|
|
stateKey: 'trainerVisibilityService',
|
|
methods: ['onDisplayTrainerChanged', 'onVisibleTrainerChanged', 'onRunningTrainerChanged'],
|
|
label: 'Trainer visibility service',
|
|
},
|
|
{
|
|
stateKey: 'trainerApiService',
|
|
methods: [
|
|
'getLatestLocalTrainerForGame',
|
|
'getMostCompatibleTrainerForGame',
|
|
'getTrainerById',
|
|
],
|
|
label: 'Trainer API service',
|
|
},
|
|
{
|
|
stateKey: 'trainerService',
|
|
methods: ['launch', 'endTrainer', 'onNewTrainer', 'onTrainerEnded'],
|
|
label: 'Trainer service',
|
|
},
|
|
];
|
|
|
|
export function resolveOptionalServices(state, container, webpackRequire) {
|
|
for (const spec of OPTIONAL_SERVICE_SPECS) {
|
|
if (!state[spec.stateKey]) {
|
|
state[spec.stateKey] = resolveOptionalService(state, container, webpackRequire, spec);
|
|
}
|
|
}
|
|
|
|
if (!state.trainerLaunchRequestCtor) {
|
|
state.trainerLaunchRequestCtor = getTrainerLaunchRequestCtor(state, webpackRequire);
|
|
}
|
|
}
|
|
|
|
export function getInstalledAppsService(state, container, webpackRequire) {
|
|
const ctor = findExportedConstructor(webpackRequire, (prototype) => {
|
|
return (
|
|
typeof prototype.refreshApps === 'function' && typeof prototype.watchGame === 'function'
|
|
);
|
|
});
|
|
|
|
if (!ctor) {
|
|
state.log('warn', 'Installed apps service constructor not found in webpack cache.');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const service = container.get(ctor);
|
|
const appsCount = isRecord(service.installedApps)
|
|
? Object.keys(service.installedApps).length
|
|
: -1;
|
|
const catalogGamesCount = isRecord(service.catalog?.games)
|
|
? Object.keys(service.catalog.games).length
|
|
: -1;
|
|
state.log(
|
|
'info',
|
|
'Installed apps service resolved.',
|
|
`ctor=${ctor.name || '<anon>'}, installedApps=${appsCount}, catalogGames=${catalogGamesCount}`,
|
|
);
|
|
return service;
|
|
} catch (error) {
|
|
state.log(
|
|
'warn',
|
|
'Failed to resolve installed apps service from Aurelia container.',
|
|
formatError(error),
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function getStoreRef(state, container, webpackRequire) {
|
|
const storeCtor = findExportedConstructor(webpackRequire, (prototype) => {
|
|
return (
|
|
typeof prototype.dispatch === 'function' &&
|
|
typeof prototype.registerAction === 'function' &&
|
|
typeof prototype.unregisterAction === 'function'
|
|
);
|
|
});
|
|
|
|
if (!storeCtor) {
|
|
state.log('warn', 'Store constructor not found in webpack cache.');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const store = container.get(storeCtor);
|
|
const storeState =
|
|
typeof store.state?.getValue === 'function' ? store.state.getValue() : null;
|
|
const installedAppsCount = isRecord(storeState?.installedApps)
|
|
? Object.keys(storeState.installedApps).length
|
|
: -1;
|
|
const catalogGamesCount = isRecord(storeState?.catalog?.games)
|
|
? Object.keys(storeState.catalog.games).length
|
|
: -1;
|
|
state.log(
|
|
'info',
|
|
'Store resolved via fallback.',
|
|
`ctor=${storeCtor.name || '<anon>'}, installedApps=${installedAppsCount}, catalogGames=${catalogGamesCount}`,
|
|
);
|
|
return store;
|
|
} catch (error) {
|
|
state.log('warn', 'Failed to resolve Store from container.', formatError(error));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function resolveOptionalService(state, container, webpackRequire, spec) {
|
|
const matchesMethods = (target) => hasAllMethods(target, spec.methods);
|
|
const ctor = findExportedConstructor(webpackRequire, matchesMethods);
|
|
if (ctor) {
|
|
return getContainerService(state, container, ctor, spec.stateKey, spec.label);
|
|
}
|
|
|
|
return findFallbackService(
|
|
state,
|
|
container,
|
|
spec.stateKey,
|
|
`${spec.label} constructor not found in webpack cache.`,
|
|
matchesMethods,
|
|
);
|
|
}
|
|
|
|
function hasAllMethods(target, methods) {
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
for (const method of methods) {
|
|
if (typeof target[method] !== 'function') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getTrainerLaunchRequestCtor(state, webpackRequire) {
|
|
const cache = webpackRequire?.c;
|
|
if (!cache || typeof cache !== 'object') {
|
|
warnMissingOptionalService(
|
|
state,
|
|
'trainerLaunchRequestCtor',
|
|
'Trainer launch request constructor cache is unavailable.',
|
|
);
|
|
return null;
|
|
}
|
|
|
|
for (const record of Object.values(cache)) {
|
|
const exports = record?.exports;
|
|
if (!isRecord(exports)) {
|
|
continue;
|
|
}
|
|
|
|
const candidate = exports[TRAINER_LAUNCH_REQUEST_EXPORT_KEY];
|
|
if (
|
|
typeof candidate === 'function' &&
|
|
typeof exports.ZS === 'function' &&
|
|
typeof exports.jR === 'function' &&
|
|
typeof exports.UY === 'function'
|
|
) {
|
|
clearMissingOptionalServiceWarning(state, 'trainerLaunchRequestCtor');
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
warnMissingOptionalService(
|
|
state,
|
|
'trainerLaunchRequestCtor',
|
|
'Trainer launch request constructor not found in webpack cache.',
|
|
);
|
|
return null;
|
|
}
|
|
|
|
function getContainerService(state, container, ctor, warningKey, label) {
|
|
try {
|
|
const service = container.get(ctor);
|
|
clearMissingOptionalServiceWarning(state, warningKey);
|
|
state.log(
|
|
'info',
|
|
`${label} resolved.`,
|
|
`ctor=${ctor.name || '<anon>'}${service?.runningTrainer ? ', running=yes' : ''}`,
|
|
);
|
|
return service;
|
|
} catch (error) {
|
|
state.log(
|
|
'warn',
|
|
`Failed to resolve ${label.toLowerCase()} from Aurelia container.`,
|
|
formatError(error),
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function findFallbackService(state, container, warningKey, missingMessage, predicate) {
|
|
const fallbackService = findInstanceInContainerGraph(container, predicate);
|
|
if (fallbackService) {
|
|
clearMissingOptionalServiceWarning(state, warningKey);
|
|
state.log('info', `${warningKey} resolved from container graph.`);
|
|
return fallbackService;
|
|
}
|
|
|
|
warnMissingOptionalService(state, warningKey, missingMessage);
|
|
return null;
|
|
}
|
|
|
|
function warnMissingOptionalService(state, key, message) {
|
|
if (state.missingOptionalServiceWarnings.has(key)) {
|
|
return;
|
|
}
|
|
|
|
state.missingOptionalServiceWarnings.add(key);
|
|
state.log('warn', message);
|
|
}
|
|
|
|
function clearMissingOptionalServiceWarning(state, key) {
|
|
state.missingOptionalServiceWarnings.delete(key);
|
|
}
|