this repo has no description
at v1.0.2 4.7 kB view raw
1import electron, { 2 BrowserWindowConstructorOptions, 3 BrowserWindow as ElectronBrowserWindow, 4 ipcMain, 5 app 6} from "electron"; 7import Module from "module"; 8import { constants } from "@moonlight-mod/types"; 9import { readConfig } from "@moonlight-mod/core/config"; 10import { getExtensions } from "@moonlight-mod/core/extension"; 11import Logger from "@moonlight-mod/core/util/logger"; 12import { 13 loadExtensions, 14 loadProcessedExtensions 15} from "core/src/extension/loader"; 16import EventEmitter from "events"; 17 18const logger = new Logger("injector"); 19 20let oldPreloadPath = ""; 21let corsAllow: string[] = []; 22 23ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 24 e.returnValue = oldPreloadPath; 25}); 26ipcMain.on(constants.ipcGetAppData, (e) => { 27 e.returnValue = app.getPath("appData"); 28}); 29ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 30 electron.dialog.showMessageBoxSync(opts); 31}); 32ipcMain.handle(constants.ipcSetCorsList, (_, list) => { 33 corsAllow = list; 34}); 35 36function patchCsp(headers: Record<string, string[]>) { 37 const directives = [ 38 "style-src", 39 "connect-src", 40 "img-src", 41 "font-src", 42 "media-src", 43 "worker-src", 44 "prefetch-src" 45 ]; 46 const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"]; 47 48 const csp = "content-security-policy"; 49 if (headers[csp] == null) return; 50 51 // This parsing is jank af lol 52 const entries = headers[csp][0] 53 .trim() 54 .split(";") 55 .map((x) => x.trim()) 56 .filter((x) => x.length > 0) 57 .map((x) => x.split(" ")) 58 .map((x) => [x[0], x.slice(1)]); 59 const parts = Object.fromEntries(entries); 60 61 for (const directive of directives) { 62 parts[directive] = values; 63 } 64 65 const stringified = Object.entries<string[]>(parts) 66 .map(([key, value]) => { 67 return `${key} ${value.join(" ")}`; 68 }) 69 .join("; "); 70 headers[csp] = [stringified]; 71} 72 73class BrowserWindow extends ElectronBrowserWindow { 74 constructor(opts: BrowserWindowConstructorOptions) { 75 oldPreloadPath = opts.webPreferences!.preload!; 76 opts.webPreferences!.preload = require.resolve("./node-preload.js"); 77 78 moonlightHost.events.emit("window-options", opts); 79 super(opts); 80 moonlightHost.events.emit("window-created", this); 81 82 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 83 if (details.responseHeaders != null) { 84 if (details.resourceType === "mainFrame") { 85 patchCsp(details.responseHeaders); 86 } 87 88 if (corsAllow.some((x) => details.url.startsWith(x))) { 89 details.responseHeaders["access-control-allow-origin"] = ["*"]; 90 } 91 92 cb({ cancel: false, responseHeaders: details.responseHeaders }); 93 } 94 }); 95 } 96} 97 98export async function inject(asarPath: string) { 99 try { 100 const config = readConfig(); 101 const extensions = getExtensions(); 102 103 // Duplicated in node-preload... oops 104 // eslint-disable-next-line no-inner-declarations 105 function getConfig(ext: string) { 106 const val = config.extensions[ext]; 107 if (val == null || typeof val === "boolean") return undefined; 108 return val.config; 109 } 110 111 global.moonlightHost = { 112 asarPath, 113 config, 114 events: new EventEmitter(), 115 extensions, 116 processedExtensions: { 117 extensions: [], 118 dependencyGraph: new Map() 119 }, 120 121 getConfig, 122 getConfigOption: <T>(ext: string, name: string) => { 123 const config = getConfig(ext); 124 if (config == null) return undefined; 125 const option = config[name]; 126 if (option == null) return undefined; 127 return option as T; 128 }, 129 getLogger: (id: string) => { 130 return new Logger(id); 131 } 132 }; 133 134 patchElectron(); 135 136 global.moonlightHost.processedExtensions = await loadExtensions(extensions); 137 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 138 } catch (e) { 139 logger.error("Failed to inject", e); 140 } 141 142 require(asarPath); 143} 144 145function patchElectron() { 146 const electronClone = {}; 147 148 for (const property of Object.getOwnPropertyNames(electron)) { 149 if (property === "BrowserWindow") { 150 Object.defineProperty(electronClone, property, { 151 get: () => BrowserWindow, 152 enumerable: true, 153 configurable: false 154 }); 155 } else { 156 Object.defineProperty( 157 electronClone, 158 property, 159 Object.getOwnPropertyDescriptor(electron, property)! 160 ); 161 } 162 } 163 164 // exports is a getter only on Windows, let's do some cursed shit instead 165 const electronPath = require.resolve("electron"); 166 const cachedElectron = require.cache[electronPath]!; 167 require.cache[electronPath] = new Module(cachedElectron.id, require.main); 168 require.cache[electronPath]!.exports = electronClone; 169}