this repo has no description
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}