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