this repo has no description
at v1.0.10 6.1 kB view raw
1import { 2 ExtensionWebExports, 3 DetectedExtension, 4 ProcessedExtensions, 5 WebpackModuleFunc 6} from "@moonlight-mod/types"; 7import { readConfig } from "../config"; 8import Logger from "../util/logger"; 9import { registerPatch, registerWebpackModule } from "../patch"; 10import calculateDependencies from "../util/dependency"; 11import { createEventEmitter } from "../util/event"; 12import { registerStyles } from "../styles"; 13 14const logger = new Logger("core/extension/loader"); 15 16async function loadExt(ext: DetectedExtension) { 17 webPreload: { 18 if (ext.scripts.web != null) { 19 const source = 20 ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; 21 const fn = new Function("require", "module", "exports", source); 22 23 const module = { id: ext.id, exports: {} }; 24 fn.apply(window, [ 25 () => { 26 logger.warn("Attempted to require() from web"); 27 }, 28 module, 29 module.exports 30 ]); 31 32 const exports: ExtensionWebExports = module.exports; 33 if (exports.patches != null) { 34 let idx = 0; 35 for (const patch of exports.patches) { 36 if (Array.isArray(patch.replace)) { 37 for (const replacement of patch.replace) { 38 const newPatch = Object.assign({}, patch, { 39 replace: replacement 40 }); 41 42 registerPatch({ ...newPatch, ext: ext.id, id: idx }); 43 idx++; 44 } 45 } else { 46 registerPatch({ ...patch, ext: ext.id, id: idx }); 47 idx++; 48 } 49 } 50 } 51 52 if (exports.webpackModules != null) { 53 for (const [name, wp] of Object.entries(exports.webpackModules)) { 54 if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { 55 const func = new Function( 56 "module", 57 "exports", 58 "require", 59 ext.scripts.webpackModules[name]! 60 ) as WebpackModuleFunc; 61 registerWebpackModule({ 62 ...wp, 63 ext: ext.id, 64 id: name, 65 run: func 66 }); 67 } else { 68 registerWebpackModule({ ...wp, ext: ext.id, id: name }); 69 } 70 } 71 } 72 73 if (exports.styles != null) { 74 registerStyles( 75 exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) 76 ); 77 } 78 } 79 } 80 81 nodePreload: { 82 if (ext.scripts.nodePath != null) { 83 try { 84 const module = require(ext.scripts.nodePath); 85 moonlightNode.nativesCache[ext.id] = module; 86 } catch (e) { 87 logger.error(`Failed to load extension "${ext.id}"`, e); 88 } 89 } 90 } 91 92 injector: { 93 if (ext.scripts.hostPath != null) { 94 try { 95 require(ext.scripts.hostPath); 96 } catch (e) { 97 logger.error(`Failed to load extension "${ext.id}"`, e); 98 } 99 } 100 } 101} 102 103/* 104 This function resolves extensions and loads them, split into a few stages: 105 106 - Duplicate detection (removing multiple extensions with the same internal ID) 107 - Dependency resolution (creating a dependency graph & detecting circular dependencies) 108 - Failed dependency pruning 109 - Implicit dependency resolution (enabling extensions that are dependencies of other extensions) 110 - Loading all extensions 111 112 Instead of constructing an order from the dependency graph and loading 113 extensions synchronously, we load them in parallel asynchronously. Loading 114 extensions fires an event on completion, which allows us to await the loading 115 of another extension, resolving dependencies & load order effectively. 116*/ 117export async function loadExtensions( 118 exts: DetectedExtension[] 119): Promise<ProcessedExtensions> { 120 const config = readConfig(); 121 const items = exts 122 .map((ext) => { 123 return { 124 id: ext.id, 125 data: ext 126 }; 127 }) 128 .sort((a, b) => a.id.localeCompare(b.id)); 129 130 const [sorted, dependencyGraph] = calculateDependencies(items, { 131 fetchDep: (id) => { 132 return exts.find((x) => x.id === id) ?? null; 133 }, 134 135 getDeps: (item) => { 136 return item.data.manifest.dependencies ?? []; 137 }, 138 139 getIncompatible: (item) => { 140 return item.data.manifest.incompatible ?? []; 141 }, 142 143 getEnabled: (item) => { 144 const entry = config.extensions[item.id]; 145 if (entry == null) return false; 146 if (entry === true) return true; 147 if (typeof entry === "object" && entry.enabled === true) return true; 148 return false; 149 } 150 }); 151 152 return { 153 extensions: sorted.map((x) => x.data), 154 dependencyGraph 155 }; 156} 157 158export async function loadProcessedExtensions({ 159 extensions, 160 dependencyGraph 161}: ProcessedExtensions) { 162 const eventEmitter = createEventEmitter(); 163 const finished: Set<string> = new Set(); 164 165 logger.trace( 166 "Load stage - extension list:", 167 extensions.map((x) => x.id) 168 ); 169 170 async function loadExtWithDependencies(ext: DetectedExtension) { 171 const deps = Array.from(dependencyGraph.get(ext.id)!); 172 173 // Wait for the dependencies to finish 174 const waitPromises = deps.map( 175 (dep: string) => 176 new Promise<void>((r) => { 177 function cb(eventDep: string) { 178 if (eventDep === dep) { 179 done(); 180 } 181 } 182 183 function done() { 184 eventEmitter.removeEventListener("ext-ready", cb); 185 r(); 186 } 187 188 eventEmitter.addEventListener("ext-ready", cb); 189 if (finished.has(dep)) done(); 190 }) 191 ); 192 193 if (waitPromises.length > 0) { 194 logger.debug( 195 `Waiting on ${waitPromises.length} dependencies for "${ext.id}"` 196 ); 197 await Promise.all(waitPromises); 198 } 199 200 logger.debug(`Loading "${ext.id}"`); 201 await loadExt(ext); 202 203 finished.add(ext.id); 204 eventEmitter.dispatchEvent("ext-ready", ext.id); 205 logger.debug(`Loaded "${ext.id}"`); 206 } 207 208 webPreload: { 209 for (const ext of extensions) { 210 moonlight.enabledExtensions.add(ext.id); 211 } 212 } 213 214 logger.debug("Loading all extensions"); 215 await Promise.all(extensions.map(loadExtWithDependencies)); 216 logger.info(`Loaded ${extensions.length} extensions`); 217}