From 433c19b20a094295498b625f0e6c2538c834c4ac Mon Sep 17 00:00:00 2001 From: NotNite Date: Sun, 5 May 2024 16:47:41 -0400 Subject: [PATCH 1/6] Initial work on browser support --- build.mjs | 97 +++++++++++--- package.json | 2 + packages/browser/manifest.json | 34 +++++ packages/browser/modifyResponseHeaders.json | 19 +++ packages/browser/package.json | 8 ++ packages/browser/src/index.ts | 49 +++++++ packages/browser/tsconfig.json | 3 + .../src/moonbase/webpackModules/stores.ts | 49 ++++++- packages/core/src/config.ts | 34 ++++- packages/core/src/extension.ts | 49 ++++++- packages/core/src/extension/loader.ts | 122 ++++++++++-------- packages/core/src/util/event.ts | 62 +++++---- packages/core/src/util/logger.ts | 2 +- packages/injector/src/index.ts | 2 +- packages/node-preload/src/index.ts | 10 +- packages/types/src/index.ts | 1 + packages/web-preload/package.json | 1 + packages/web-preload/src/index.ts | 6 +- pnpm-lock.yaml | 9 ++ 19 files changed, 440 insertions(+), 119 deletions(-) create mode 100644 packages/browser/manifest.json create mode 100644 packages/browser/modifyResponseHeaders.json create mode 100644 packages/browser/package.json create mode 100644 packages/browser/src/index.ts create mode 100644 packages/browser/tsconfig.json diff --git a/build.mjs b/build.mjs index 2a51741..490f494 100644 --- a/build.mjs +++ b/build.mjs @@ -13,6 +13,7 @@ const config = { const prod = process.env.NODE_ENV === "production"; const watch = process.argv.includes("--watch"); +const browser = process.argv.includes("--browser"); const external = [ "electron", @@ -73,19 +74,26 @@ const taggedBuildLog = (tag) => ({ }); async function build(name, entry) { - const outfile = path.join("./dist", name + ".js"); + let outfile = path.join("./dist", name + ".js"); + if (name === "browser") outfile = path.join("./dist", "browser", "index.js"); const dropLabels = []; if (name !== "injector") dropLabels.push("injector"); if (name !== "node-preload") dropLabels.push("nodePreload"); if (name !== "web-preload") dropLabels.push("webPreload"); + if (name !== "browser") dropLabels.push("browser"); const define = { MOONLIGHT_ENV: `"${name}"`, MOONLIGHT_PROD: prod.toString() }; - for (const iterName of Object.keys(config)) { + for (const iterName of [ + "injector", + "node-preload", + "web-preload", + "browser" + ]) { const snake = iterName.replace(/-/g, "_").toUpperCase(); define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); } @@ -93,13 +101,29 @@ async function build(name, entry) { const nodeDependencies = ["glob"]; const ignoredExternal = name === "web-preload" ? nodeDependencies : []; + const plugins = [deduplicatedLogging, taggedBuildLog(name)]; + if (name === "browser") { + plugins.push( + copyStaticFiles({ + src: "./packages/browser/manifest.json", + dest: "./dist/browser/manifest.json" + }) + ); + plugins.push( + copyStaticFiles({ + src: "./packages/browser/modifyResponseHeaders.json", + dest: "./dist/browser/modifyResponseHeaders.json" + }) + ); + } + /** @type {import("esbuild").BuildOptions} */ const esbuildConfig = { entryPoints: [entry], outfile, format: "cjs", - platform: name === "web-preload" ? "browser" : "node", + platform: ["web-preload", "browser"].includes(name) ? "browser" : "node", treeShaking: true, bundle: true, @@ -112,9 +136,38 @@ async function build(name, entry) { dropLabels, logLevel: "silent", - plugins: [deduplicatedLogging, taggedBuildLog(name)] + plugins }; + if (name === "browser") { + const coreExtensionsJson = {}; + + // eslint-disable-next-line no-inner-declarations + function readDir(dir) { + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = dir + "/" + file; + const normalizedPath = filePath.replace("./dist/core-extensions/", ""); + if (fs.statSync(filePath).isDirectory()) { + readDir(filePath); + } else { + coreExtensionsJson[normalizedPath] = fs.readFileSync( + filePath, + "utf8" + ); + } + } + } + + readDir("./dist/core-extensions"); + + esbuildConfig.banner = { + js: `window._moonlight_coreExtensionsStr = ${JSON.stringify( + JSON.stringify(coreExtensionsJson) + )};` + }; + } + if (watch) { const ctx = await esbuild.context(esbuildConfig); await ctx.watch(); @@ -210,23 +263,27 @@ async function buildExt(ext, side, copyManifest, fileExt) { const promises = []; -for (const [name, entry] of Object.entries(config)) { - promises.push(build(name, entry)); -} +if (browser) { + build("browser", "packages/browser/src/index.ts"); +} else { + for (const [name, entry] of Object.entries(config)) { + promises.push(build(name, entry)); + } -const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); -for (const ext of coreExtensions) { - let copiedManifest = false; - - for (const fileExt of ["ts", "tsx"]) { - for (const type of ["index", "node", "host"]) { - if ( - fs.existsSync( - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` - ) - ) { - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); - copiedManifest = true; + const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); + for (const ext of coreExtensions) { + let copiedManifest = false; + + for (const fileExt of ["ts", "tsx"]) { + for (const type of ["index", "node", "host"]) { + if ( + fs.existsSync( + `./packages/core-extensions/src/${ext}/${type}.${fileExt}` + ) + ) { + promises.push(buildExt(ext, type, !copiedManifest, fileExt)); + copiedManifest = true; + } } } } diff --git a/package.json b/package.json index 32f99c7..c2ba342 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "scripts": { "build": "node build.mjs", "dev": "node build.mjs --watch", + "browser": "node build.mjs --browser", + "browser-dev": "node build.mjs --browser --watch", "lint": "eslint packages", "lint:fix": "eslint packages", "lint:report": "eslint --output-file eslint_report.json --format json packages", diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json new file mode 100644 index 0000000..8600cab --- /dev/null +++ b/packages/browser/manifest.json @@ -0,0 +1,34 @@ +{ + "manifest_version": 3, + "name": "moonlight", + "description": "Yet another Discord mod", + "version": "1.0.8", + "permissions": ["declarativeNetRequestWithHostAccess"], + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ], + "web_accessible_resources": [ + { + "resources": ["core-extensions/*"], + "matches": ["https://*.discord.com/*"] + } + ], + "host_permissions": [ + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], + "declarative_net_request": { + "rule_resources": [ + { + "id": "modifyResponseHeaders", + "enabled": true, + "path": "modifyResponseHeaders.json" + } + ] + } +} diff --git a/packages/browser/modifyResponseHeaders.json b/packages/browser/modifyResponseHeaders.json new file mode 100644 index 0000000..51ce30d --- /dev/null +++ b/packages/browser/modifyResponseHeaders.json @@ -0,0 +1,19 @@ +[ + { + "id": 1, + "priority": 1, + "action": { + "type": "modifyHeaders", + "responseHeaders": [ + { + "header": "Content-Security-Policy", + "operation": "remove" + } + ] + }, + "condition": { + "resourceTypes": ["main_frame"], + "initiatorDomains": ["discord.com"] + } + } +] diff --git a/packages/browser/package.json b/packages/browser/package.json new file mode 100644 index 0000000..3fa3e4b --- /dev/null +++ b/packages/browser/package.json @@ -0,0 +1,8 @@ +{ + "name": "@moonlight-mod/browser", + "private": true, + "dependencies": { + "@moonlight-mod/core": "workspace:*", + "@moonlight-mod/web-preload": "workspace:*" + } +} diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts new file mode 100644 index 0000000..90a12b0 --- /dev/null +++ b/packages/browser/src/index.ts @@ -0,0 +1,49 @@ +import load from "@moonlight-mod/web-preload"; +import { readConfig, writeConfig } from "@moonlight-mod/core/config"; +import Logger from "@moonlight-mod/core/util/logger"; +import { getExtensions } from "@moonlight-mod/core/extension"; +import { loadExtensions } from "@moonlight-mod/core/extension/loader"; + +// Mostly copy pasted from node-preload, FIXME +// TODO: is this safe an in IIFE? +(async () => { + const config = readConfig(); + const extensions = await getExtensions(); + const processedExtensions = await loadExtensions(extensions); + + function getConfig(ext: string) { + const val = config.extensions[ext]; + if (val == null || typeof val === "boolean") return undefined; + return val.config; + } + + Object.assign(window, { + moonlightNode: { + config, + extensions, + processedExtensions, + nativesCache: {}, + + getConfig, + getConfigOption: (ext: string, name: string) => { + const config = getConfig(ext); + if (config == null) return undefined; + const option = config[name]; + if (option == null) return undefined; + return option as T; + }, + getNatives: () => {}, + getLogger: (id: string) => { + return new Logger(id); + }, + + getExtensionDir: (ext: string) => { + return `/extensions/${ext}`; + }, + + writeConfig + } + }); + + await load(); +})(); diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/packages/browser/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 6f920bd..7f480d2 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,11 +1,50 @@ import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; -import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; +import { + ExtensionState, + MoonbaseExtension, + MoonbaseNatives, + RepositoryManifest +} from "../types"; import Flux from "@moonlight-mod/wp/common_flux"; import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher"; -const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); const logger = moonlight.getLogger("moonbase"); +let natives: MoonbaseNatives | undefined = moonlight.getNatives("moonbase"); +if (!natives) { + natives = { + fetchRepositories: async (repos) => { + const ret: Record = {}; + + for (const repo of repos) { + try { + const req = await fetch(repo); + const json = await req.json(); + ret[repo] = json; + } catch (e) { + logger.error(`Error fetching repository ${repo}`, e); + } + } + + return ret; + }, + installExtension: async (manifest, url, repo) => { + // TODO + }, + deleteExtension: async (id) => { + // TODO + }, + getExtensionConfig: (id, key) => { + const config = moonlightNode.config.extensions[id]; + if (typeof config === "object") { + return config.config?.[key]; + } + + return undefined; + } + }; +} + class MoonbaseSettingsStore extends Flux.Store { private origConfig: Config; private config: Config; @@ -42,7 +81,7 @@ class MoonbaseSettingsStore extends Flux.Store { }; } - natives.fetchRepositories(this.config.repositories).then((ret) => { + natives!.fetchRepositories(this.config.repositories).then((ret) => { for (const [repo, exts] of Object.entries(ret)) { try { for (const ext of exts) { @@ -217,7 +256,7 @@ class MoonbaseSettingsStore extends Flux.Store { this.installing = true; try { const url = this.updates[uniqueId]?.download ?? ext.manifest.download; - await natives.installExtension(ext.manifest, url, ext.source.url!); + await natives!.installExtension(ext.manifest, url, ext.source.url!); if (ext.state === ExtensionState.NotDownloaded) { this.extensions[uniqueId].state = ExtensionState.Disabled; } @@ -237,7 +276,7 @@ class MoonbaseSettingsStore extends Flux.Store { this.installing = true; try { - await natives.deleteExtension(ext.id); + await natives!.deleteExtension(ext.id); this.extensions[uniqueId].state = ExtensionState.NotDownloaded; } catch (e) { logger.error("Error deleting extension:", e); diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index eefa38a..16e48b5 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -13,9 +13,19 @@ const defaultConfig: Config = { }; export function writeConfig(config: Config) { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + nodePreload: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + return; + } + + browser: { + localStorage.setItem("moonlight-config", JSON.stringify(config)); + return; + } + + throw new Error("Called writeConfig() in an impossible environment"); } function readConfigNode(): Config { @@ -36,6 +46,20 @@ function readConfigNode(): Config { return config; } +function readConfigBrowser(): Config { + const configStr = localStorage.getItem("moonlight-config"); + if (!configStr) { + writeConfig(defaultConfig); + return defaultConfig; + } + + let config: Config = JSON.parse(configStr); + config = { ...defaultConfig, ...config }; + writeConfig(config); + + return config; +} + export function readConfig(): Config { webPreload: { return moonlightNode.config; @@ -49,5 +73,9 @@ export function readConfig(): Config { return readConfigNode(); } + browser: { + return readConfigBrowser(); + } + throw new Error("Called readConfig() in an impossible environment"); } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index f9d1be5..0c30ece 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -124,7 +124,50 @@ function getExtensionsNative(): DetectedExtension[] { return res; } -export function getExtensions(): DetectedExtension[] { +async function getExtensionsBrowser(): Promise { + const res: DetectedExtension[] = []; + + const coreExtensionsFs: Record = JSON.parse( + // @ts-expect-error shut up + _moonlight_coreExtensionsStr + ); + const coreExtensions = Array.from( + new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])) + ); + + for (const ext of coreExtensions) { + if (!coreExtensionsFs[`${ext}/index.js`]) continue; + const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]); + const web = coreExtensionsFs[`${ext}/index.js`]; + + const wpModules: Record = {}; + const wpModulesPath = `${ext}/webpackModules`; + for (const wpModuleFile of Object.keys(coreExtensionsFs)) { + if (wpModuleFile.startsWith(wpModulesPath)) { + wpModules[ + wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "") + ] = coreExtensionsFs[wpModuleFile]; + } + } + + res.push({ + id: manifest.id, + manifest, + source: { + type: ExtensionLoadSource.Core + }, + scripts: { + web, + webpackModules: wpModules + } + }); + } + + // TODO: normal extensions + return res; +} + +export async function getExtensions(): Promise { webPreload: { return moonlightNode.extensions; } @@ -137,5 +180,9 @@ export function getExtensions(): DetectedExtension[] { return getExtensionsNative(); } + browser: { + return getExtensionsBrowser(); + } + throw new Error("Called getExtensions() outside of node-preload/web-preload"); } diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index b7d696a..1cdcc4e 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -13,70 +13,78 @@ import { registerStyles } from "../styles"; const logger = new Logger("core/extension/loader"); -async function loadExt(ext: DetectedExtension) { - webPreload: { - if (ext.scripts.web != null) { - const source = - ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; - const fn = new Function("require", "module", "exports", source); - - const module = { id: ext.id, exports: {} }; - fn.apply(window, [ - () => { - logger.warn("Attempted to require() from web"); - }, - module, - module.exports - ]); - - const exports: ExtensionWebExports = module.exports; - if (exports.patches != null) { - let idx = 0; - for (const patch of exports.patches) { - if (Array.isArray(patch.replace)) { - for (const replacement of patch.replace) { - const newPatch = Object.assign({}, patch, { - replace: replacement - }); - - registerPatch({ ...newPatch, ext: ext.id, id: idx }); - idx++; - } - } else { - registerPatch({ ...patch, ext: ext.id, id: idx }); +function loadExtWeb(ext: DetectedExtension) { + if (ext.scripts.web != null) { + const source = + ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; + const fn = new Function("require", "module", "exports", source); + + const module = { id: ext.id, exports: {} }; + fn.apply(window, [ + () => { + logger.warn("Attempted to require() from web"); + }, + module, + module.exports + ]); + + const exports: ExtensionWebExports = module.exports; + if (exports.patches != null) { + let idx = 0; + for (const patch of exports.patches) { + if (Array.isArray(patch.replace)) { + for (const replacement of patch.replace) { + const newPatch = Object.assign({}, patch, { + replace: replacement + }); + + registerPatch({ ...newPatch, ext: ext.id, id: idx }); idx++; } + } else { + registerPatch({ ...patch, ext: ext.id, id: idx }); + idx++; } } + } - if (exports.webpackModules != null) { - for (const [name, wp] of Object.entries(exports.webpackModules)) { - if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { - const func = new Function( - "module", - "exports", - "require", - ext.scripts.webpackModules[name]! - ) as WebpackModuleFunc; - registerWebpackModule({ - ...wp, - ext: ext.id, - id: name, - run: func - }); - } else { - registerWebpackModule({ ...wp, ext: ext.id, id: name }); - } + if (exports.webpackModules != null) { + for (const [name, wp] of Object.entries(exports.webpackModules)) { + if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { + const func = new Function( + "module", + "exports", + "require", + ext.scripts.webpackModules[name]! + ) as WebpackModuleFunc; + registerWebpackModule({ + ...wp, + ext: ext.id, + id: name, + run: func + }); + } else { + registerWebpackModule({ ...wp, ext: ext.id, id: name }); } } + } - if (exports.styles != null) { - registerStyles( - exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) - ); - } + if (exports.styles != null) { + registerStyles( + exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) + ); } } +} + +async function loadExt(ext: DetectedExtension) { + webPreload: { + loadExtWeb(ext); + } + + browser: { + loadExtWeb(ext); + } nodePreload: { if (ext.scripts.nodePath != null) { @@ -211,6 +219,12 @@ export async function loadProcessedExtensions({ } } + browser: { + for (const ext of extensions) { + moonlight.enabledExtensions.add(ext.id); + } + } + logger.debug("Loading all extensions"); await Promise.all(extensions.map(loadExtWithDependencies)); logger.info(`Loaded ${extensions.length} extensions`); diff --git a/packages/core/src/util/event.ts b/packages/core/src/util/event.ts index dd606e1..7eda9a9 100644 --- a/packages/core/src/util/event.ts +++ b/packages/core/src/util/event.ts @@ -6,6 +6,36 @@ export interface MoonlightEventEmitter { removeEventListener: (id: string, cb: MoonlightEventCallback) => void; } +function webMethod(): MoonlightEventEmitter { + const eventEmitter = new EventTarget(); + const listeners = new Map void>(); + + return { + dispatchEvent: (id: string, data: string) => { + eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); + }, + + addEventListener: (id: string, cb: (data: string) => void) => { + if (listeners.has(cb)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event.detail); + } + + listeners.set(cb, listener); + eventEmitter.addEventListener(id, listener); + }, + + removeEventListener: (id: string, cb: (data: string) => void) => { + const listener = listeners.get(cb); + if (listener == null) return; + listeners.delete(cb); + eventEmitter.removeEventListener(id, listener); + } + }; +} + function nodeMethod(): MoonlightEventEmitter { const EventEmitter = require("events"); const eventEmitter = new EventEmitter(); @@ -38,33 +68,7 @@ function nodeMethod(): MoonlightEventEmitter { export function createEventEmitter(): MoonlightEventEmitter { webPreload: { - const eventEmitter = new EventTarget(); - const listeners = new Map void>(); - - return { - dispatchEvent: (id: string, data: string) => { - eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); - }, - - addEventListener: (id: string, cb: (data: string) => void) => { - if (listeners.has(cb)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event.detail); - } - - listeners.set(cb, listener); - eventEmitter.addEventListener(id, listener); - }, - - removeEventListener: (id: string, cb: (data: string) => void) => { - const listener = listeners.get(cb); - if (listener == null) return; - listeners.delete(cb); - eventEmitter.removeEventListener(id, listener); - } - }; + return webMethod(); } nodePreload: { @@ -75,5 +79,9 @@ export function createEventEmitter(): MoonlightEventEmitter { return nodeMethod(); } + browser: { + return webMethod(); + } + throw new Error("Called createEventEmitter() in an impossible environment"); } diff --git a/packages/core/src/util/logger.ts b/packages/core/src/util/logger.ts index 96488a1..55f5a5b 100644 --- a/packages/core/src/util/logger.ts +++ b/packages/core/src/util/logger.ts @@ -57,7 +57,7 @@ export default class Logger { const logLevel = LogLevel[level].toUpperCase(); if (maxLevel > level) return; - if (MOONLIGHT_WEB_PRELOAD) { + if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { args = [ `%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index a235b45..ceb5b2e 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -161,7 +161,7 @@ export async function inject(asarPath: string) { isMoonlightDesktop = asarPath === "moonlightDesktop"; try { const config = readConfig(); - const extensions = getExtensions(); + const extensions = await getExtensions(); // Duplicated in node-preload... oops // eslint-disable-next-line no-inner-declarations diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 179fef2..ea914f7 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -14,8 +14,8 @@ import { async function injectGlobals() { const config = readConfig(); - const extensions = getExtensions(); - const processed = await loadExtensions(extensions); + const extensions = await getExtensions(); + const processedExtensions = await loadExtensions(extensions); function getConfig(ext: string) { const val = config.extensions[ext]; @@ -25,8 +25,8 @@ async function injectGlobals() { global.moonlightNode = { config, - extensions: getExtensions(), - processedExtensions: processed, + extensions, + processedExtensions, nativesCache: {}, getConfig, getConfigOption: (ext: string, name: string) => { @@ -48,7 +48,7 @@ async function injectGlobals() { writeConfig }; - await loadProcessedExtensions(processed); + await loadProcessedExtensions(processedExtensions); contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); const extCors = moonlightNode.processedExtensions.extensions diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2aef490..03e2688 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -25,6 +25,7 @@ declare global { const MOONLIGHT_INJECTOR: boolean; const MOONLIGHT_NODE_PRELOAD: boolean; const MOONLIGHT_WEB_PRELOAD: boolean; + const MOONLIGHT_BROWSER: boolean; var moonlightHost: MoonlightHost; var moonlightNode: MoonlightNode; diff --git a/packages/web-preload/package.json b/packages/web-preload/package.json index 419ef88..be672b2 100644 --- a/packages/web-preload/package.json +++ b/packages/web-preload/package.json @@ -1,6 +1,7 @@ { "name": "@moonlight-mod/web-preload", "private": true, + "main": "src/index.ts", "dependencies": { "@moonlight-mod/core": "workspace:*" } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index eaebeaa..a039b9f 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -3,7 +3,7 @@ import { installWebpackPatcher } from "@moonlight-mod/core/patch"; import { installStyles } from "@moonlight-mod/core/styles"; import Logger from "@moonlight-mod/core/util/logger"; -(async () => { +export default async function load() { const logger = new Logger("web-preload"); window.moonlight = { @@ -29,4 +29,6 @@ import Logger from "@moonlight-mod/core/util/logger"; window.addEventListener("DOMContentLoaded", () => { installStyles(); }); -})(); +} + +if (MOONLIGHT_ENV === "web-preload") load(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c853f61..c71a023 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,15 @@ importers: specifier: ^5.3.2 version: 5.3.2 + packages/browser: + dependencies: + '@moonlight-mod/core': + specifier: workspace:* + version: link:../core + '@moonlight-mod/web-preload': + specifier: workspace:* + version: link:../web-preload + packages/core: dependencies: '@moonlight-mod/types': From a546230762b93ce514023b728d77d1631780689b Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 13:52:20 -0400 Subject: [PATCH 2/6] Rest of the fucking owl --- build.mjs | 34 ++- packages/browser/manifest.json | 2 +- packages/browser/package.json | 5 +- packages/browser/src/index.ts | 140 ++++++++-- packages/core-extensions/package.json | 4 +- packages/core-extensions/src/moonbase/node.ts | 15 +- .../src/moonbase/webpackModules/stores.ts | 25 +- packages/core/src/asar.ts | 57 ++++ packages/core/src/config.ts | 91 +++---- packages/core/src/extension.ts | 250 ++++++++++++------ packages/core/src/extension/loader.ts | 16 +- packages/core/src/util/binary.ts | 66 +++++ packages/core/src/util/event.ts | 184 ++++++------- packages/core/src/util/logger.ts | 20 +- packages/injector/src/index.ts | 5 +- packages/node-preload/src/index.ts | 5 +- packages/types/src/globals.ts | 17 ++ packages/types/src/index.ts | 2 + packages/web-preload/src/index.ts | 3 +- pnpm-lock.yaml | 177 +++++++++++-- 20 files changed, 779 insertions(+), 339 deletions(-) create mode 100644 packages/core/src/asar.ts create mode 100644 packages/core/src/util/binary.ts diff --git a/build.mjs b/build.mjs index 40fc76b..3144738 100644 --- a/build.mjs +++ b/build.mjs @@ -20,8 +20,6 @@ const external = [ "fs", "path", "module", - "events", - "original-fs", // wtf asar? "discord", // mappings // Silence an esbuild warning @@ -79,10 +77,20 @@ async function build(name, entry) { if (name === "browser") outfile = path.join("./dist", "browser", "index.js"); const dropLabels = []; - if (name !== "injector") dropLabels.push("injector"); - if (name !== "node-preload") dropLabels.push("nodePreload"); - if (name !== "web-preload") dropLabels.push("webPreload"); - if (name !== "browser") dropLabels.push("browser"); + const labels = { + injector: ["injector"], + nodePreload: ["node-preload"], + webPreload: ["web-preload"], + browser: ["browser"], + + webTarget: ["web-preload", "browser"], + nodeTarget: ["node-preload", "injector"] + }; + for (const [label, targets] of Object.entries(labels)) { + if (!targets.includes(name)) { + dropLabels.push(label); + } + } const define = { MOONLIGHT_ENV: `"${name}"`, @@ -116,6 +124,20 @@ async function build(name, entry) { dest: "./dist/browser/modifyResponseHeaders.json" }) ); + + // This sucks lmfao + plugins.push({ + name: "browserPath", + setup(build) { + build.onResolve({ filter: /^path$/ }, () => { + const index = + "./packages/browser/node_modules/path-browserify/index.js"; + return { + path: path.resolve(index) + }; + }); + } + }); } /** @type {import("esbuild").BuildOptions} */ diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 8600cab..1f51179 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "moonlight", "description": "Yet another Discord mod", - "version": "1.0.8", + "version": "1.1.0", "permissions": ["declarativeNetRequestWithHostAccess"], "content_scripts": [ { diff --git a/packages/browser/package.json b/packages/browser/package.json index 3fa3e4b..fe8a4f8 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -3,6 +3,9 @@ "private": true, "dependencies": { "@moonlight-mod/core": "workspace:*", - "@moonlight-mod/web-preload": "workspace:*" + "@moonlight-mod/types": "workspace:*", + "@moonlight-mod/web-preload": "workspace:*", + "@zenfs/core": "^1.0.2", + "@zenfs/dom": "^0.2.16" } } diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 0ff4635..9bf9f1c 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,13 +1,96 @@ import "@moonlight-mod/web-preload"; import { readConfig, writeConfig } from "@moonlight-mod/core/config"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; +import { MoonlightBrowserFS, MoonlightNode } from "@moonlight-mod/types"; + +import { IndexedDB } from "@zenfs/dom"; +import { configure } from "@zenfs/core"; +import * as fs from "@zenfs/core/promises"; // Mostly copy pasted from node-preload, FIXME // TODO: is this safe an in IIFE? (async () => { - const config = readConfig(); + // Set up a virtual filesystem with IndexedDB + await configure({ + mounts: { + "/": { + backend: IndexedDB, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore tsc tweaking + storeName: "moonlight-fs" + } + } + }); + + const browserFS: MoonlightBrowserFS = { + async readFile(path) { + return new Uint8Array(await fs.readFile(path)); + }, + async writeFile(path, data) { + await fs.writeFile(path, data); + }, + async unlink(path) { + await fs.unlink(path); + }, + + async readdir(path) { + return await fs.readdir(path); + }, + async mkdir(path) { + const parts = this.parts(path); + for (let i = 0; i < parts.length; i++) { + const path = this.join(...parts.slice(0, i + 1)); + if (!(await this.exists(path))) await fs.mkdir(path); + } + }, + + async rmdir(path) { + const entries = await this.readdir(path); + + for (const entry of entries) { + const fullPath = this.join(path, entry); + const isFile = await this.isFile(fullPath); + if (isFile) { + await this.unlink(fullPath); + } else { + await this.rmdir(fullPath); + } + } + + await fs.rmdir(path); + }, + + async exists(path) { + return await fs.exists(path); + }, + async isFile(path) { + return (await fs.stat(path)).isFile(); + }, + + join(...parts) { + let str = parts.join("/"); + if (!str.startsWith("/")) str = "/" + str; + return str; + }, + dirname(path) { + const parts = this.parts(path); + return "/" + parts.slice(0, parts.length - 1).join("/"); + }, + parts(path) { + if (path.startsWith("/")) path = path.substring(1); + return path.split("/"); + } + }; + Object.assign(window, { + _moonlightBrowserFS: browserFS + }); + + // Actual loading begins here + const config = await readConfig(); + initLogger(config); + const extensions = await getExtensions(); const processedExtensions = await loadExtensions(extensions); @@ -17,33 +100,36 @@ import { loadExtensions } from "@moonlight-mod/core/extension/loader"; return val.config; } + const moonlightNode: MoonlightNode = { + config, + extensions, + processedExtensions, + nativesCache: {}, + + getConfig, + getConfigOption: (ext: string, name: string) => { + const config = getConfig(ext); + if (config == null) return undefined; + const option = config[name]; + if (option == null) return undefined; + return option as T; + }, + getNatives: () => {}, + getLogger: (id: string) => { + return new Logger(id); + }, + + getExtensionDir: (ext: string) => { + return `/extensions/${ext}`; + }, + + writeConfig + }; + Object.assign(window, { - moonlightNode: { - config, - extensions, - processedExtensions, - nativesCache: {}, - - getConfig, - getConfigOption: (ext: string, name: string) => { - const config = getConfig(ext); - if (config == null) return undefined; - const option = config[name]; - if (option == null) return undefined; - return option as T; - }, - getNatives: () => {}, - getLogger: (id: string) => { - return new Logger(id); - }, - - getExtensionDir: (ext: string) => { - return `/extensions/${ext}`; - }, - - writeConfig - } + moonlightNode }); + // This is set by web-preload for us await window._moonlightLoad(); })(); diff --git a/packages/core-extensions/package.json b/packages/core-extensions/package.json index 11d57f6..d90313f 100644 --- a/packages/core-extensions/package.json +++ b/packages/core-extensions/package.json @@ -2,7 +2,7 @@ "name": "@moonlight-mod/core-extensions", "private": true, "dependencies": { - "@electron/asar": "^3.2.5", - "@moonlight-mod/types": "workspace:*" + "@moonlight-mod/types": "workspace:*", + "@moonlight-mod/core": "workspace:*" } } diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index ce44165..69c063c 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -1,9 +1,7 @@ import { MoonbaseNatives, RepositoryManifest } from "./types"; -import asar from "@electron/asar"; import fs from "fs"; import path from "path"; -import os from "os"; -import { repoUrlFile } from "types/src/constants"; +import extractAsar from "@moonlight-mod/core/asar"; const logger = moonlightNode.getLogger("moonbase"); @@ -36,13 +34,12 @@ async function installExtension( fs.mkdirSync(dir, { recursive: true }); // for some reason i just can't .writeFileSync() a file that ends in .asar??? - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-")); - const tempFile = path.join(tempDir, "extension"); const buffer = await req.arrayBuffer(); - fs.writeFileSync(tempFile, Buffer.from(buffer)); - - asar.extractAll(tempFile, dir); - fs.writeFileSync(path.join(dir, repoUrlFile), repo); + const files = extractAsar(buffer); + for (const [file, buf] of Object.entries(files)) { + const nodeBuf = Buffer.from(buf); + fs.writeFileSync(path.join(dir, file), nodeBuf); + } } async function deleteExtension(id: string) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 46ddcc3..5dbc653 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -7,11 +7,13 @@ import { } from "../types"; import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import extractAsar from "@moonlight-mod/core/asar"; const logger = moonlight.getLogger("moonbase"); -let natives: MoonbaseNatives | undefined = moonlight.getNatives("moonbase"); -if (!natives) { +let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); +if (window._moonlightBrowserFS != null) { + const browserFS = window._moonlightBrowserFS!; natives = { fetchRepositories: async (repos) => { const ret: Record = {}; @@ -29,10 +31,25 @@ if (!natives) { return ret; }, installExtension: async (manifest, url, repo) => { - // TODO + const req = await fetch(url); + const buffer = await req.arrayBuffer(); + + if (await browserFS.exists("/extensions/" + manifest.id)) { + await browserFS.rmdir("/extensions/" + manifest.id); + } + + const files = extractAsar(buffer); + for (const [file, data] of Object.entries(files)) { + const path = + "/extensions/" + + manifest.id + + (file.startsWith("/") ? file : `/${file}`); + await browserFS.mkdir(browserFS.dirname(path)); + await browserFS.writeFile(path, data); + } }, deleteExtension: async (id) => { - // TODO + browserFS.rmdir("/extensions/" + id); }, getExtensionConfig: (id, key) => { const config = moonlightNode.config.extensions[id]; diff --git a/packages/core/src/asar.ts b/packages/core/src/asar.ts new file mode 100644 index 0000000..1f2f166 --- /dev/null +++ b/packages/core/src/asar.ts @@ -0,0 +1,57 @@ +// https://github.com/electron/asar +// http://formats.kaitai.io/python_pickle/ +import { BinaryReader } from "./util/binary"; + +/* + The asar format is kinda bad, especially because it uses multiple pickle + entries. It spams sizes, expecting us to read small buffers and parse those, + but we can just take it all through at once without having to create multiple + BinaryReaders. This implementation might be wrong, though. + + This either has size/offset or files but I can't get the type to cooperate, + so pretend this is a union. +*/ + +type AsarEntry = { + size: number; + offset: `${number}`; // who designed this + + files?: Record; +}; + +export default function extractAsar(file: ArrayBuffer) { + const array = new Uint8Array(file); + const br = new BinaryReader(array); + + // two uints, one containing the number '4', to signify that the other uint takes up 4 bytes + // bravo, electron, bravo + const _payloadSize = br.readUInt32(); + const _headerSize = br.readInt32(); + + const headerStringStart = br.position; + const headerStringSize = br.readUInt32(); // How big the block is + const actualStringSize = br.readUInt32(); // How big the string in that block is + + const base = headerStringStart + headerStringSize + 4; + + const string = br.readString(actualStringSize); + const header: AsarEntry = JSON.parse(string); + + const ret: Record = {}; + function addDirectory(dir: AsarEntry, path: string) { + for (const [name, data] of Object.entries(dir.files!)) { + const fullName = path + "/" + name; + if (data.files != null) { + addDirectory(data, fullName); + } else { + br.position = base + parseInt(data.offset); + const file = br.read(data.size); + ret[fullName] = file; + } + } + } + + addDirectory(header, ""); + + return ret; +} diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 94207de..db941e8 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -12,79 +12,60 @@ const defaultConfig: Config = { repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] }; -function writeConfigNode(config: Config) { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - return; -} - export function writeConfig(config: Config) { - nodePreload: { - writeConfigNode(config); - return; - } - - injector: { - writeConfigNode(config); + browser: { + const enc = new TextEncoder().encode(JSON.stringify(config, null, 2)); + window._moonlightBrowserFS!.writeFile("/config.json", enc); return; } - browser: { - localStorage.setItem("moonlight-config", JSON.stringify(config)); + nodeTarget: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); return; } throw new Error("Called writeConfig() in an impossible environment"); } -function readConfigNode(): Config { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - - if (!fs.existsSync(configPath)) { - writeConfig(defaultConfig); - return defaultConfig; +export async function readConfig(): Promise { + webPreload: { + return moonlightNode.config; } - let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); - - // Assign the default values if they don't exist (newly added) - config = { ...defaultConfig, ...config }; - writeConfig(config); - - return config; -} - -function readConfigBrowser(): Config { - const configStr = localStorage.getItem("moonlight-config"); - if (!configStr) { - writeConfig(defaultConfig); - return defaultConfig; + browser: { + if (await window._moonlightBrowserFS!.exists("/config.json")) { + const file = await window._moonlightBrowserFS!.readFile("/config.json"); + const configStr = new TextDecoder().decode(file); + let config: Config = JSON.parse(configStr); + + config = { ...defaultConfig, ...config }; + writeConfig(config); + + return config; + } else { + writeConfig(defaultConfig); + return defaultConfig; + } } - let config: Config = JSON.parse(configStr); - config = { ...defaultConfig, ...config }; - writeConfig(config); + nodeTarget: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); - return config; -} + if (!fs.existsSync(configPath)) { + writeConfig(defaultConfig); + return defaultConfig; + } -export function readConfig(): Config { - webPreload: { - return moonlightNode.config; - } + let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); - nodePreload: { - return readConfigNode(); - } + // Assign the default values if they don't exist (newly added) + config = { ...defaultConfig, ...config }; + writeConfig(config); - injector: { - return readConfigNode(); - } - - browser: { - return readConfigBrowser(); + return config; } throw new Error("Called readConfig() in an impossible environment"); diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index eb73890..c072ca6 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -7,20 +7,87 @@ import { import { readConfig } from "./config"; import requireImport from "./util/import"; import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; +import Logger from "./util/logger"; + +const logger = new Logger("core/extension"); + +// This is kinda duplicated from the browser FS type but idc +interface MoonlightFSWrapper { + readdir(path: string): Promise; + exists(path: string): Promise; + isFile(path: string): Promise; + readFile(path: string): Promise; + join(...parts: string[]): string; + dirname(path: string): string; +} + +function getFS(): MoonlightFSWrapper { + browser: { + const fs = window._moonlightBrowserFS!; + return { + async readdir(path) { + return await fs.readdir(path); + }, + async exists(path) { + return await fs.exists(path); + }, + async isFile(path) { + return await fs.isFile(path); + }, + async readFile(path) { + const buf = await fs.readFile(path); + const text = new TextDecoder().decode(buf); + return text; + }, + join(...parts) { + return fs.join(...parts); + }, + dirname(path) { + return fs.dirname(path); + } + }; + } -function findManifests(dir: string): string[] { const fs = requireImport("fs"); const path = requireImport("path"); + + return { + async readdir(path) { + return fs.readdirSync(path); + }, + async exists(path) { + return fs.existsSync(path); + }, + async isFile(path) { + return fs.statSync(path).isFile(); + }, + async readFile(path) { + return fs.readFileSync(path, "utf8"); + }, + join(...parts) { + return path.join(...parts); + }, + dirname(dir) { + return path.dirname(dir); + } + }; +} + +async function findManifests( + fs: MoonlightFSWrapper, + dir: string +): Promise { const ret = []; - if (fs.existsSync(dir)) { - for (const file of fs.readdirSync(dir)) { + if (await fs.exists(dir)) { + for (const file of await fs.readdir(dir)) { + const path = fs.join(dir, file); if (file === "manifest.json") { - ret.push(path.join(dir, file)); + ret.push(path); } - if (fs.statSync(path.join(dir, file)).isDirectory()) { - ret.push(...findManifests(path.join(dir, file))); + if (!(await fs.isFile(path))) { + ret.push(...(await findManifests(fs, path))); } } } @@ -28,100 +95,111 @@ function findManifests(dir: string): string[] { return ret; } -function loadDetectedExtensions( +async function loadDetectedExtensions( + fs: MoonlightFSWrapper, dir: string, type: ExtensionLoadSource -): DetectedExtension[] { - const fs = requireImport("fs"); - const path = requireImport("path"); +): Promise { const ret: DetectedExtension[] = []; - const manifests = findManifests(dir); + const manifests = await findManifests(fs, dir); for (const manifestPath of manifests) { - if (!fs.existsSync(manifestPath)) continue; - const dir = path.dirname(manifestPath); + try { + if (!(await fs.exists(manifestPath))) continue; + const dir = fs.dirname(manifestPath); - const manifest: ExtensionManifest = JSON.parse( - fs.readFileSync(manifestPath, "utf8") - ); - const level = manifest.apiLevel ?? 1; - if (level !== constants.apiLevel) { - continue; - } + const manifest: ExtensionManifest = JSON.parse( + await fs.readFile(manifestPath) + ); + const level = manifest.apiLevel ?? 1; + if (level !== constants.apiLevel) { + continue; + } - const webPath = path.join(dir, "index.js"); - const nodePath = path.join(dir, "node.js"); - const hostPath = path.join(dir, "host.js"); - - // if none exist (empty manifest) don't give a shit - if ( - !fs.existsSync(webPath) && - !fs.existsSync(nodePath) && - !fs.existsSync(hostPath) - ) { - continue; - } + const webPath = fs.join(dir, "index.js"); + const nodePath = fs.join(dir, "node.js"); + const hostPath = fs.join(dir, "host.js"); - const web = fs.existsSync(webPath) - ? fs.readFileSync(webPath, "utf8") - : undefined; + // if none exist (empty manifest) don't give a shit + if (!fs.exists(webPath) && !fs.exists(nodePath) && !fs.exists(hostPath)) { + continue; + } - let url: string | undefined = undefined; - const urlPath = path.join(dir, constants.repoUrlFile); - if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) { - url = fs.readFileSync(urlPath, "utf8"); - } + const web = (await fs.exists(webPath)) + ? await fs.readFile(webPath) + : undefined; - const wpModules: Record = {}; - const wpModulesPath = path.join(dir, "webpackModules"); - if (fs.existsSync(wpModulesPath)) { - const wpModulesFile = fs.readdirSync(wpModulesPath); - - for (const wpModuleFile of wpModulesFile) { - if (wpModuleFile.endsWith(".js")) { - wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync( - path.join(wpModulesPath, wpModuleFile), - "utf8" - ); - } + let url: string | undefined = undefined; + const urlPath = fs.join(dir, constants.repoUrlFile); + if (type === ExtensionLoadSource.Normal && (await fs.exists(urlPath))) { + url = await fs.readFile(urlPath); } - } - ret.push({ - id: manifest.id, - manifest, - source: { - type, - url - }, - scripts: { - web, - webPath: web != null ? webPath : undefined, - webpackModules: wpModules, - nodePath: fs.existsSync(nodePath) ? nodePath : undefined, - hostPath: fs.existsSync(hostPath) ? hostPath : undefined + const wpModules: Record = {}; + const wpModulesPath = fs.join(dir, "webpackModules"); + if (await fs.exists(wpModulesPath)) { + const wpModulesFile = await fs.readdir(wpModulesPath); + + for (const wpModuleFile of wpModulesFile) { + if (wpModuleFile.endsWith(".js")) { + wpModules[wpModuleFile.replace(".js", "")] = await fs.readFile( + fs.join(wpModulesPath, wpModuleFile) + ); + } + } } - }); + + ret.push({ + id: manifest.id, + manifest, + source: { + type, + url + }, + scripts: { + web, + webPath: web != null ? webPath : undefined, + webpackModules: wpModules, + nodePath: (await fs.exists(nodePath)) ? nodePath : undefined, + hostPath: (await fs.exists(hostPath)) ? hostPath : undefined + } + }); + } catch (e) { + logger.error(e, "Failed to load extension"); + } } return ret; } -function getExtensionsNative(): DetectedExtension[] { - const config = readConfig(); +async function getExtensionsNative(): Promise { + const config = await readConfig(); const res = []; + const fs = getFS(); res.push( - ...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core) + ...(await loadDetectedExtensions( + fs, + getCoreExtensionsPath(), + ExtensionLoadSource.Core + )) ); res.push( - ...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal) + ...(await loadDetectedExtensions( + fs, + getExtensionsPath(), + ExtensionLoadSource.Normal + )) ); for (const devSearchPath of config.devSearchPaths ?? []) { res.push( - ...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer) + ...(await loadDetectedExtensions( + fs, + devSearchPath, + ExtensionLoadSource.Developer + )) ); } @@ -129,7 +207,7 @@ function getExtensionsNative(): DetectedExtension[] { } async function getExtensionsBrowser(): Promise { - const res: DetectedExtension[] = []; + const ret: DetectedExtension[] = []; const coreExtensionsFs: Record = JSON.parse( // @ts-expect-error shut up @@ -154,7 +232,7 @@ async function getExtensionsBrowser(): Promise { } } - res.push({ + ret.push({ id: manifest.id, manifest, source: { @@ -167,8 +245,16 @@ async function getExtensionsBrowser(): Promise { }); } - // TODO: normal extensions - return res; + const fs = getFS(); + ret.push( + ...(await loadDetectedExtensions( + fs, + "/extensions", + ExtensionLoadSource.Normal + )) + ); + + return ret; } export async function getExtensions(): Promise { @@ -176,16 +262,12 @@ export async function getExtensions(): Promise { return moonlightNode.extensions; } - nodePreload: { - return getExtensionsNative(); - } - - injector: { - return getExtensionsNative(); + browser: { + return await getExtensionsBrowser(); } - browser: { - return getExtensionsBrowser(); + nodeTarget: { + return await getExtensionsNative(); } throw new Error("Called getExtensions() outside of node-preload/web-preload"); diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index cd65472..db1c414 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -78,11 +78,7 @@ function loadExtWeb(ext: DetectedExtension) { } async function loadExt(ext: DetectedExtension) { - webPreload: { - loadExtWeb(ext); - } - - browser: { + webTarget: { loadExtWeb(ext); } @@ -125,7 +121,7 @@ async function loadExt(ext: DetectedExtension) { export async function loadExtensions( exts: DetectedExtension[] ): Promise { - const config = readConfig(); + const config = await readConfig(); const items = exts .map((ext) => { return { @@ -213,13 +209,7 @@ export async function loadProcessedExtensions({ logger.debug(`Loaded "${ext.id}"`); } - webPreload: { - for (const ext of extensions) { - moonlight.enabledExtensions.add(ext.id); - } - } - - browser: { + webTarget: { for (const ext of extensions) { moonlight.enabledExtensions.add(ext.id); } diff --git a/packages/core/src/util/binary.ts b/packages/core/src/util/binary.ts new file mode 100644 index 0000000..421f8c9 --- /dev/null +++ b/packages/core/src/util/binary.ts @@ -0,0 +1,66 @@ +// https://github.com/NotNite/brc-save-editor/blob/main/src/lib/binary.ts +export interface BinaryInterface { + data: Uint8Array; + view: DataView; + length: number; + position: number; +} + +export class BinaryReader implements BinaryInterface { + data: Uint8Array; + view: DataView; + length: number; + position: number; + + constructor(data: Uint8Array) { + this.data = data; + this.view = new DataView(data.buffer); + + this.length = data.length; + this.position = 0; + } + + readByte() { + return this._read(this.view.getInt8, 1); + } + + readBoolean() { + return this.readByte() !== 0; + } + + readInt32() { + return this._read(this.view.getInt32, 4); + } + + readUInt32() { + return this._read(this.view.getUint32, 4); + } + + readSingle() { + return this._read(this.view.getFloat32, 4); + } + + readInt64() { + return this._read(this.view.getBigInt64, 8); + } + + readString(length: number) { + const result = this.read(length); + return new TextDecoder().decode(result); + } + + read(length: number) { + const data = this.data.subarray(this.position, this.position + length); + this.position += length; + return data; + } + + private _read( + func: (position: number, littleEndian?: boolean) => T, + length: number + ): T { + const result = func.call(this.view, this.position, true); + this.position += length; + return result; + } +} diff --git a/packages/core/src/util/event.ts b/packages/core/src/util/event.ts index 49bf6dc..8458c6f 100644 --- a/packages/core/src/util/event.ts +++ b/packages/core/src/util/event.ts @@ -1,114 +1,92 @@ import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event"; -function nodeMethod< - EventId extends string = string, - EventData = Record ->(): MoonlightEventEmitter { - const EventEmitter = require("events"); - const eventEmitter = new EventEmitter(); - const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); - - return { - dispatchEvent: ( - id: Id, - data: EventData[Id] - ) => { - eventEmitter.emit(id as string, data); - }, - - addEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - if (listeners.has(untyped)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event as EventData[Id]); - } - - listeners.set(untyped, listener); - eventEmitter.on(id as string, listener); - }, - - removeEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - const listener = listeners.get(untyped); - if (listener == null) return; - listeners.delete(untyped); - eventEmitter.off(id as string, listener); - } - }; -} - -function webMethod< - EventId extends string = string, - EventData = Record ->(): MoonlightEventEmitter { - const eventEmitter = new EventTarget(); - const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); - - return { - dispatchEvent: ( - id: Id, - data: EventData[Id] - ) => { - eventEmitter.dispatchEvent( - new CustomEvent(id as string, { detail: data }) - ); - }, - - addEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - if (listeners.has(untyped)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event.detail as EventData[Id]); - } - - listeners.set(untyped, listener); - eventEmitter.addEventListener(id as string, listener); - }, - - removeEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - const listener = listeners.get(untyped); - if (listener == null) return; - listeners.delete(untyped); - eventEmitter.removeEventListener(id as string, listener); - } - }; -} - export function createEventEmitter< EventId extends string = string, EventData = Record >(): MoonlightEventEmitter { - webPreload: { - return webMethod(); - } - - nodePreload: { - return nodeMethod(); - } - - injector: { - return nodeMethod(); + webTarget: { + const eventEmitter = new EventTarget(); + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); + + return { + dispatchEvent: ( + id: Id, + data: EventData[Id] + ) => { + eventEmitter.dispatchEvent( + new CustomEvent(id as string, { detail: data }) + ); + }, + + addEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + if (listeners.has(untyped)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event.detail as EventData[Id]); + } + + listeners.set(untyped, listener); + eventEmitter.addEventListener(id as string, listener); + }, + + removeEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + const listener = listeners.get(untyped); + if (listener == null) return; + listeners.delete(untyped); + eventEmitter.removeEventListener(id as string, listener); + } + }; } - browser: { - return webMethod(); + nodeTarget: { + const EventEmitter = require("events"); + const eventEmitter = new EventEmitter(); + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); + + return { + dispatchEvent: ( + id: Id, + data: EventData[Id] + ) => { + eventEmitter.emit(id as string, data); + }, + + addEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + if (listeners.has(untyped)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event as EventData[Id]); + } + + listeners.set(untyped, listener); + eventEmitter.on(id as string, listener); + }, + + removeEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + const listener = listeners.get(untyped); + if (listener == null) return; + listeners.delete(untyped); + eventEmitter.off(id as string, listener); + } + }; } throw new Error("Called createEventEmitter() in an impossible environment"); diff --git a/packages/core/src/util/logger.ts b/packages/core/src/util/logger.ts index 55f5a5b..080a8c3 100644 --- a/packages/core/src/util/logger.ts +++ b/packages/core/src/util/logger.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { LogLevel } from "@moonlight-mod/types/logger"; -import { readConfig } from "../config"; +import { Config } from "@moonlight-mod/types"; const colors = { [LogLevel.SILLY]: "#EDD3E9", @@ -11,15 +11,7 @@ const colors = { [LogLevel.ERROR]: "#FF0000" }; -const config = readConfig(); let maxLevel = LogLevel.INFO; -if (config.loggerLevel != null) { - const enumValue = - LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; - if (enumValue != null) { - maxLevel = enumValue; - } -} export default class Logger { private name: string; @@ -92,3 +84,13 @@ export default class Logger { } } } + +export function initLogger(config: Config) { + if (config.loggerLevel != null) { + const enumValue = + LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; + if (enumValue != null) { + maxLevel = enumValue; + } + } +} diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 4f57d02..562a5ec 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -8,7 +8,7 @@ import Module from "node:module"; import { constants } from "@moonlight-mod/types"; import { readConfig } from "@moonlight-mod/core/config"; import { getExtensions } from "@moonlight-mod/core/extension"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { loadExtensions, loadProcessedExtensions @@ -161,7 +161,8 @@ Object.defineProperty(BrowserWindow, "name", { export async function inject(asarPath: string) { isMoonlightDesktop = asarPath === "moonlightDesktop"; try { - const config = readConfig(); + const config = await readConfig(); + initLogger(config); const extensions = await getExtensions(); // Duplicated in node-preload... oops diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index ea914f7..85817b7 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -6,14 +6,15 @@ import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import { constants } from "@moonlight-mod/types"; import { getExtensions } from "@moonlight-mod/core/extension"; import { getExtensionsPath } from "@moonlight-mod/core/util/data"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; async function injectGlobals() { - const config = readConfig(); + const config = await readConfig(); + initLogger(config); const extensions = await getExtensions(); const processedExtensions = await loadExtensions(extensions); diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index adc6c7f..08a438d 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -38,6 +38,23 @@ export type MoonlightNode = { writeConfig: (config: Config) => void; }; +export type MoonlightBrowserFS = { + readFile: (path: string) => Promise; + writeFile: (path: string, data: Uint8Array) => Promise; + unlink: (path: string) => Promise; + + readdir: (path: string) => Promise; + mkdir: (path: string) => Promise; + rmdir: (path: string) => Promise; + + exists: (path: string) => Promise; + isFile: (path: string) => Promise; + + join: (...parts: string[]) => string; + dirname: (path: string) => string; + parts: (path: string) => string[]; +}; + export type MoonlightWeb = { unpatched: Set; pendingModules: Set; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5bf11aa..fa87414 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -5,6 +5,7 @@ /* eslint-disable no-var */ import { + MoonlightBrowserFS, MoonlightEnv, MoonlightHost, MoonlightNode, @@ -35,4 +36,5 @@ declare global { var moonlight: MoonlightWeb; var _moonlightLoad: () => Promise; + var _moonlightBrowserFS: MoonlightBrowserFS | undefined; } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index b4e5f18..d958a7b 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -7,7 +7,7 @@ import { } from "@moonlight-mod/core/patch"; import { constants } from "@moonlight-mod/types"; import { installStyles } from "@moonlight-mod/core/styles"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import LunAST from "@moonlight-mod/lunast"; import Moonmap from "@moonlight-mod/moonmap"; import loadMappings from "@moonlight-mod/mappings"; @@ -15,6 +15,7 @@ import { createEventEmitter } from "@moonlight-mod/core/util/event"; import { EventPayloads, EventType } from "@moonlight-mod/types/core/event"; async function load() { + initLogger(moonlightNode.config); const logger = new Logger("web-preload"); window.moonlight = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d7170b..8f02261 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,18 @@ importers: '@moonlight-mod/core': specifier: workspace:* version: link:../core + '@moonlight-mod/types': + specifier: workspace:* + version: link:../types '@moonlight-mod/web-preload': specifier: workspace:* version: link:../web-preload + '@zenfs/core': + specifier: ^1.0.2 + version: 1.0.2 + '@zenfs/dom': + specifier: ^0.2.16 + version: 0.2.16(@zenfs/core@1.0.2) packages/core: dependencies: @@ -59,9 +68,9 @@ importers: packages/core-extensions: dependencies: - '@electron/asar': - specifier: ^3.2.5 - version: 3.2.5 + '@moonlight-mod/core': + specifier: workspace:* + version: link:../core '@moonlight-mod/types': specifier: workspace:* version: link:../types @@ -129,11 +138,6 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} - '@electron/asar@3.2.5': - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} - engines: {node: '>=10.12.0'} - hasBin: true - '@esbuild/android-arm64@0.19.3': resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} engines: {node: '>=12'} @@ -343,12 +347,18 @@ packages: '@types/node@18.17.17': resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} + '@types/node@20.16.10': + resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} '@types/react@18.3.10': resolution: {integrity: sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==} + '@types/readable-stream@4.0.15': + resolution: {integrity: sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==} + '@types/semver@7.5.6': resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} @@ -413,6 +423,21 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@zenfs/core@1.0.2': + resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==} + engines: {node: '>= 16'} + hasBin: true + + '@zenfs/dom@0.2.16': + resolution: {integrity: sha512-6Ev+ol9hZIgQECNZR+xxjQ/a99EhhrWeiQttm/+U7YJK3HdTjiKfU39DsfGeH64vSqhpa5Vj+LWRx75SHkjw0Q==} + engines: {node: '>= 18'} + peerDependencies: + '@zenfs/core': ^1.0.0 + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -477,6 +502,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -488,10 +516,16 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -514,10 +548,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -667,6 +697,17 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -809,6 +850,9 @@ packages: engines: {node: '>=14'} hasBin: true + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -1026,6 +1070,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1144,6 +1192,10 @@ packages: engines: {node: '>=14'} hasBin: true + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1157,6 +1209,10 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + reflect.getprototypeof@1.0.4: resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} engines: {node: '>= 0.4'} @@ -1193,6 +1249,12 @@ packages: resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -1247,6 +1309,9 @@ packages: string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1326,6 +1391,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -1333,6 +1401,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + utilium@0.7.1: + resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -1366,12 +1437,6 @@ snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} - '@electron/asar@3.2.5': - dependencies: - commander: 5.1.0 - glob: 7.2.3 - minimatch: 3.1.2 - '@esbuild/android-arm64@0.19.3': optional: true @@ -1527,6 +1592,10 @@ snapshots: '@types/node@18.17.17': {} + '@types/node@20.16.10': + dependencies: + undici-types: 6.19.8 + '@types/prop-types@15.7.13': {} '@types/react@18.3.10': @@ -1534,6 +1603,11 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/readable-stream@4.0.15': + dependencies: + '@types/node': 20.16.10 + safe-buffer: 5.1.2 + '@types/semver@7.5.6': {} '@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2)': @@ -1623,6 +1697,24 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@zenfs/core@1.0.2': + dependencies: + '@types/node': 20.16.10 + '@types/readable-stream': 4.0.15 + buffer: 6.0.3 + eventemitter3: 5.0.1 + minimatch: 9.0.5 + readable-stream: 4.5.2 + utilium: 0.7.1 + + '@zenfs/dom@0.2.16(@zenfs/core@1.0.2)': + dependencies: + '@zenfs/core': 1.0.2 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -1701,6 +1793,8 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + big-integer@1.6.52: {} bplist-parser@0.2.0: @@ -1712,10 +1806,19 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.2: dependencies: fill-range: 7.0.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bundle-name@3.0.0: dependencies: run-applescript: 5.0.0 @@ -1739,8 +1842,6 @@ snapshots: color-name@1.1.4: {} - commander@5.1.0: {} - concat-map@0.0.1: {} cross-spawn@7.0.3: @@ -2007,6 +2108,12 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + eventemitter3@5.0.1: {} + + events@3.3.0: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -2169,6 +2276,8 @@ snapshots: husky@8.0.3: {} + ieee754@1.2.1: {} + ignore@5.3.0: {} import-fresh@3.3.0: @@ -2369,6 +2478,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + ms@2.1.2: {} natural-compare@1.4.0: {} @@ -2481,6 +2594,8 @@ snapshots: prettier@3.1.0: {} + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -2493,6 +2608,14 @@ snapshots: react-is@16.13.1: {} + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + reflect.getprototypeof@1.0.4: dependencies: call-bind: 1.0.5 @@ -2537,6 +2660,10 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safe-regex-test@1.0.0: dependencies: call-bind: 1.0.5 @@ -2612,6 +2739,10 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.22.3 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2689,12 +2820,18 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + undici-types@6.19.8: {} + untildify@4.0.0: {} uri-js@4.4.1: dependencies: punycode: 2.3.1 + utilium@0.7.1: + dependencies: + eventemitter3: 5.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 From c9d0ba9b464841da27137fcf246ef7966273a7d8 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:12:26 -0400 Subject: [PATCH 3/6] Use mkdir when installing extension --- packages/core-extensions/src/moonbase/node.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 69c063c..5d34e56 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -38,6 +38,10 @@ async function installExtension( const files = extractAsar(buffer); for (const [file, buf] of Object.entries(files)) { const nodeBuf = Buffer.from(buf); + const fullFile = path.join(dir, file); + const fullDir = path.dirname(fullFile); + + if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true }); fs.writeFileSync(path.join(dir, file), nodeBuf); } } From e765c5e37b9813d9eed7ce3e431087b2ec00f4e8 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:17:11 -0400 Subject: [PATCH 4/6] Write repo URL file --- packages/core-extensions/src/moonbase/node.ts | 4 +++- .../core-extensions/src/moonbase/webpackModules/stores.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 5d34e56..22b30f7 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -2,6 +2,7 @@ import { MoonbaseNatives, RepositoryManifest } from "./types"; import fs from "fs"; import path from "path"; import extractAsar from "@moonlight-mod/core/asar"; +import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlightNode.getLogger("moonbase"); @@ -33,7 +34,6 @@ async function installExtension( if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true }); - // for some reason i just can't .writeFileSync() a file that ends in .asar??? const buffer = await req.arrayBuffer(); const files = extractAsar(buffer); for (const [file, buf] of Object.entries(files)) { @@ -44,6 +44,8 @@ async function installExtension( if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true }); fs.writeFileSync(path.join(dir, file), nodeBuf); } + + fs.writeFileSync(path.join(dir, repoUrlFile), repo); } async function deleteExtension(id: string) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 5dbc653..36e7360 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -8,6 +8,7 @@ import { import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; import extractAsar from "@moonlight-mod/core/asar"; +import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlight.getLogger("moonbase"); @@ -47,6 +48,11 @@ if (window._moonlightBrowserFS != null) { await browserFS.mkdir(browserFS.dirname(path)); await browserFS.writeFile(path, data); } + + await browserFS.writeFile( + `/extensions/${manifest.id}/` + repoUrlFile, + new TextEncoder().encode(repo) + ); }, deleteExtension: async (id) => { browserFS.rmdir("/extensions/" + id); From d1b7f4d6e2ac3ad84cbc134877b6439eda5e1a5b Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:33:14 -0400 Subject: [PATCH 5/6] Add mv2 support --- build.mjs | 27 ++++++++++++--------------- package.json | 2 +- packages/browser/manifest.json | 6 ------ packages/browser/manifestv2.json | 23 +++++++++++++++++++++++ packages/browser/src/background.js | 12 ++++++++++++ 5 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 packages/browser/manifestv2.json create mode 100644 packages/browser/src/background.js diff --git a/build.mjs b/build.mjs index 3144738..b825584 100644 --- a/build.mjs +++ b/build.mjs @@ -14,6 +14,7 @@ const config = { const prod = process.env.NODE_ENV === "production"; const watch = process.argv.includes("--watch"); const browser = process.argv.includes("--browser"); +const mv2 = process.argv.includes("--mv2"); const external = [ "electron", @@ -114,7 +115,9 @@ async function build(name, entry) { if (name === "browser") { plugins.push( copyStaticFiles({ - src: "./packages/browser/manifest.json", + src: mv2 + ? "./packages/browser/manifestv2.json" + : "./packages/browser/manifest.json", dest: "./dist/browser/manifest.json" }) ); @@ -124,20 +127,14 @@ async function build(name, entry) { dest: "./dist/browser/modifyResponseHeaders.json" }) ); - - // This sucks lmfao - plugins.push({ - name: "browserPath", - setup(build) { - build.onResolve({ filter: /^path$/ }, () => { - const index = - "./packages/browser/node_modules/path-browserify/index.js"; - return { - path: path.resolve(index) - }; - }); - } - }); + if (mv2) { + plugins.push( + copyStaticFiles({ + src: "./packages/browser/src/background.js", + dest: "./dist/browser/background.js" + }) + ); + } } /** @type {import("esbuild").BuildOptions} */ diff --git a/package.json b/package.json index 902ed49..4f1ad30 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build": "node build.mjs", "dev": "node build.mjs --watch", "browser": "node build.mjs --browser", - "browser-dev": "node build.mjs --browser --watch", + "browser-mv2": "node build.mjs --browser --mv2", "lint": "eslint packages", "lint:fix": "eslint packages", "lint:report": "eslint --output-file eslint_report.json --format json packages", diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 1f51179..7477ce4 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -12,12 +12,6 @@ "world": "MAIN" } ], - "web_accessible_resources": [ - { - "resources": ["core-extensions/*"], - "matches": ["https://*.discord.com/*"] - } - ], "host_permissions": [ "https://moonlight-mod.github.io/*", "https://*.discord.com/*" diff --git a/packages/browser/manifestv2.json b/packages/browser/manifestv2.json new file mode 100644 index 0000000..e120c24 --- /dev/null +++ b/packages/browser/manifestv2.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "moonlight", + "description": "Yet another Discord mod", + "version": "1.1.0", + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ], + "permissions": [ + "webRequest", + "webRequestBlocking", + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], + "background": { + "scripts": ["background.js"] + } +} diff --git a/packages/browser/src/background.js b/packages/browser/src/background.js new file mode 100644 index 0000000..dca4d6f --- /dev/null +++ b/packages/browser/src/background.js @@ -0,0 +1,12 @@ +// This is so tiny that I don't need to esbuild it + +// eslint-disable-next-line no-undef +chrome.webRequest.onHeadersReceived.addListener( + (details) => ({ + responseHeaders: details.responseHeaders.filter( + (header) => header.name.toLowerCase() !== "content-security-policy" + ) + }), + { urls: ["https://*.discord.com/*"] }, + ["blocking", "responseHeaders"] +); From 36cf7589107befb3f788eaefbd25f643c22c1a85 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 19:34:06 -0400 Subject: [PATCH 6/6] Delay Discord loading in the worst way possible --- build.mjs | 28 +++-- packages/browser/blockLoading.json | 13 ++ packages/browser/manifest.json | 32 ++++- packages/browser/manifestv2.json | 22 ++-- packages/browser/modifyResponseHeaders.json | 2 +- packages/browser/src/background-mv2.js | 99 ++++++++++++++++ packages/browser/src/background.js | 124 ++++++++++++++++++-- packages/browser/src/index.ts | 9 +- packages/browser/tsconfig.json | 5 +- packages/core/src/extension.ts | 16 +-- packages/types/src/index.ts | 3 +- packages/web-preload/src/index.ts | 2 +- 12 files changed, 302 insertions(+), 53 deletions(-) create mode 100644 packages/browser/blockLoading.json create mode 100644 packages/browser/src/background-mv2.js diff --git a/build.mjs b/build.mjs index b825584..c898b99 100644 --- a/build.mjs +++ b/build.mjs @@ -121,20 +121,30 @@ async function build(name, entry) { dest: "./dist/browser/manifest.json" }) ); - plugins.push( - copyStaticFiles({ - src: "./packages/browser/modifyResponseHeaders.json", - dest: "./dist/browser/modifyResponseHeaders.json" - }) - ); - if (mv2) { + + if (!mv2) { plugins.push( copyStaticFiles({ - src: "./packages/browser/src/background.js", - dest: "./dist/browser/background.js" + src: "./packages/browser/modifyResponseHeaders.json", + dest: "./dist/browser/modifyResponseHeaders.json" + }) + ); + plugins.push( + copyStaticFiles({ + src: "./packages/browser/blockLoading.json", + dest: "./dist/browser/blockLoading.json" }) ); } + + plugins.push( + copyStaticFiles({ + src: mv2 + ? "./packages/browser/src/background-mv2.js" + : "./packages/browser/src/background.js", + dest: "./dist/browser/background.js" + }) + ); } /** @type {import("esbuild").BuildOptions} */ diff --git a/packages/browser/blockLoading.json b/packages/browser/blockLoading.json new file mode 100644 index 0000000..252bbd9 --- /dev/null +++ b/packages/browser/blockLoading.json @@ -0,0 +1,13 @@ +[ + { + "id": 2, + "priority": 1, + "action": { + "type": "block" + }, + "condition": { + "urlFilter": "*://discord.com/assets/*.js", + "resourceTypes": ["script"] + } + } +] diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 7477ce4..f0bea16 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -3,7 +3,16 @@ "name": "moonlight", "description": "Yet another Discord mod", "version": "1.1.0", - "permissions": ["declarativeNetRequestWithHostAccess"], + "permissions": [ + "declarativeNetRequestWithHostAccess", + "webRequest", + "scripting", + "webNavigation" + ], + "host_permissions": [ + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], "content_scripts": [ { "js": ["index.js"], @@ -12,17 +21,28 @@ "world": "MAIN" } ], - "host_permissions": [ - "https://moonlight-mod.github.io/*", - "https://*.discord.com/*" - ], "declarative_net_request": { "rule_resources": [ { "id": "modifyResponseHeaders", "enabled": true, "path": "modifyResponseHeaders.json" + }, + { + "id": "blockLoading", + "enabled": true, + "path": "blockLoading.json" } ] - } + }, + "background": { + "service_worker": "background.js", + "type": "module" + }, + "web_accessible_resources": [ + { + "resources": ["index.js"], + "matches": ["https://*.discord.com/*"] + } + ] } diff --git a/packages/browser/manifestv2.json b/packages/browser/manifestv2.json index e120c24..1b97688 100644 --- a/packages/browser/manifestv2.json +++ b/packages/browser/manifestv2.json @@ -3,21 +3,23 @@ "name": "moonlight", "description": "Yet another Discord mod", "version": "1.1.0", - "content_scripts": [ - { - "js": ["index.js"], - "matches": ["https://*.discord.com/*"], - "run_at": "document_start", - "world": "MAIN" - } - ], "permissions": [ "webRequest", "webRequestBlocking", - "https://moonlight-mod.github.io/*", + "scripting", + "webNavigation", + "https://*.discord.com/assets/*.js", "https://*.discord.com/*" ], "background": { "scripts": ["background.js"] - } + }, + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ] } diff --git a/packages/browser/modifyResponseHeaders.json b/packages/browser/modifyResponseHeaders.json index 51ce30d..b8fed08 100644 --- a/packages/browser/modifyResponseHeaders.json +++ b/packages/browser/modifyResponseHeaders.json @@ -1,7 +1,7 @@ [ { "id": 1, - "priority": 1, + "priority": 2, "action": { "type": "modifyHeaders", "responseHeaders": [ diff --git a/packages/browser/src/background-mv2.js b/packages/browser/src/background-mv2.js new file mode 100644 index 0000000..33b1669 --- /dev/null +++ b/packages/browser/src/background-mv2.js @@ -0,0 +1,99 @@ +/* eslint-disable no-console */ +/* eslint-disable no-undef */ + +const starterUrls = ["web.", "sentry."]; +let blockLoading = true; +let doing = false; +let collectedUrls = new Set(); + +chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { + const url = new URL(details.url); + if (!blockLoading && url.hostname.endsWith("discord.com")) { + console.log("Blocking", details.url); + blockLoading = true; + collectedUrls.clear(); + } +}); + +async function doTheThing(urls, tabId) { + console.log("Doing", urls, tabId); + + blockLoading = false; + + try { + await chrome.scripting.executeScript({ + target: { tabId }, + world: "MAIN", + args: [urls], + func: async (urls) => { + try { + await window._moonlightBrowserInit(); + } catch (e) { + console.log(e); + } + + const scripts = [...document.querySelectorAll("script")].filter( + (script) => script.src && urls.some((url) => url.includes(script.src)) + ); + + // backwards + urls.reverse(); + for (const url of urls) { + const script = scripts.find((script) => url.includes(script.src)); + console.log("adding new script", script); + + const newScript = document.createElement("script"); + for (const { name, value } of script.attributes) { + newScript.setAttribute(name, value); + } + + script.remove(); + document.documentElement.appendChild(newScript); + } + } + }); + } catch (e) { + console.log(e); + } + + doing = false; + collectedUrls.clear(); +} + +chrome.webRequest.onBeforeRequest.addListener( + async (details) => { + if (starterUrls.some((url) => details.url.includes(url))) { + console.log("Adding", details.url); + collectedUrls.add(details.url); + } + + if (collectedUrls.size === starterUrls.length) { + if (doing) return; + if (!blockLoading) return; + doing = true; + const urls = [...collectedUrls]; + const tabId = details.tabId; + + // yes this is a load-bearing sleep + setTimeout(() => doTheThing(urls, tabId), 0); + } + + if (blockLoading) return { cancel: true }; + }, + { + urls: ["https://*.discord.com/assets/*.js"] + }, + ["blocking"] +); + +chrome.webRequest.onHeadersReceived.addListener( + (details) => { + return { + responseHeaders: details.responseHeaders.filter( + (header) => header.name.toLowerCase() !== "content-security-policy" + ) + }; + }, + { urls: ["https://*.discord.com/*"] }, + ["blocking", "responseHeaders"] +); diff --git a/packages/browser/src/background.js b/packages/browser/src/background.js index dca4d6f..46d0464 100644 --- a/packages/browser/src/background.js +++ b/packages/browser/src/background.js @@ -1,12 +1,114 @@ -// This is so tiny that I don't need to esbuild it - -// eslint-disable-next-line no-undef -chrome.webRequest.onHeadersReceived.addListener( - (details) => ({ - responseHeaders: details.responseHeaders.filter( - (header) => header.name.toLowerCase() !== "content-security-policy" - ) - }), - { urls: ["https://*.discord.com/*"] }, - ["blocking", "responseHeaders"] +/* eslint-disable no-console */ +/* eslint-disable no-undef */ + +const starterUrls = ["web.", "sentry."]; +let blockLoading = true; +let doing = false; +let collectedUrls = new Set(); + +chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { + const url = new URL(details.url); + if (!blockLoading && url.hostname.endsWith("discord.com")) { + await chrome.declarativeNetRequest.updateEnabledRulesets({ + enableRulesetIds: ["modifyResponseHeaders", "blockLoading"] + }); + blockLoading = true; + collectedUrls.clear(); + } +}); + +chrome.webRequest.onBeforeRequest.addListener( + async (details) => { + if (details.tabId === -1) return; + if (starterUrls.some((url) => details.url.includes(url))) { + console.log("Adding", details.url); + collectedUrls.add(details.url); + } + + if (collectedUrls.size === starterUrls.length) { + if (doing) return; + if (!blockLoading) return; + doing = true; + const urls = [...collectedUrls]; + console.log("Doing", urls); + + console.log("Running moonlight script"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + files: ["index.js"] + }); + } catch (e) { + console.log(e); + } + + console.log("Initializing moonlight"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + func: async () => { + try { + await window._moonlightBrowserInit(); + } catch (e) { + console.log(e); + } + } + }); + } catch (e) { + console.log(e); + } + + console.log("Updating rulesets"); + try { + blockLoading = false; + await chrome.declarativeNetRequest.updateEnabledRulesets({ + disableRulesetIds: ["blockLoading"], + enableRulesetIds: ["modifyResponseHeaders"] + }); + } catch (e) { + console.log(e); + } + + console.log("Readding scripts"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + args: [urls], + func: async (urls) => { + const scripts = [...document.querySelectorAll("script")].filter( + (script) => + script.src && urls.some((url) => url.includes(script.src)) + ); + + // backwards + urls.reverse(); + for (const url of urls) { + const script = scripts.find((script) => url.includes(script.src)); + console.log("adding new script", script); + + const newScript = document.createElement("script"); + for (const { name, value } of script.attributes) { + newScript.setAttribute(name, value); + } + + script.remove(); + document.documentElement.appendChild(newScript); + } + } + }); + } catch (e) { + console.log(e); + } + + console.log("Done"); + doing = false; + collectedUrls.clear(); + } + }, + { + urls: ["*://*.discord.com/assets/*.js"] + } ); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 9bf9f1c..75795c2 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -4,14 +4,11 @@ import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; import { MoonlightBrowserFS, MoonlightNode } from "@moonlight-mod/types"; - import { IndexedDB } from "@zenfs/dom"; import { configure } from "@zenfs/core"; import * as fs from "@zenfs/core/promises"; -// Mostly copy pasted from node-preload, FIXME -// TODO: is this safe an in IIFE? -(async () => { +window._moonlightBrowserInit = async () => { // Set up a virtual filesystem with IndexedDB await configure({ mounts: { @@ -131,5 +128,5 @@ import * as fs from "@zenfs/core/promises"; }); // This is set by web-preload for us - await window._moonlightLoad(); -})(); + await window._moonlightBrowserLoad(); +}; diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json index 4082f16..691d95d 100644 --- a/packages/browser/tsconfig.json +++ b/packages/browser/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "../../tsconfig.json" + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ES2022" + } } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index c072ca6..d20cc28 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -246,13 +246,15 @@ async function getExtensionsBrowser(): Promise { } const fs = getFS(); - ret.push( - ...(await loadDetectedExtensions( - fs, - "/extensions", - ExtensionLoadSource.Normal - )) - ); + if (await fs.exists("/extensions")) { + ret.push( + ...(await loadDetectedExtensions( + fs, + "/extensions", + ExtensionLoadSource.Normal + )) + ); + } return ret; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index fa87414..5930b8f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -35,6 +35,7 @@ declare global { var moonlightNode: MoonlightNode; var moonlight: MoonlightWeb; - var _moonlightLoad: () => Promise; + var _moonlightBrowserInit: () => Promise; + var _moonlightBrowserLoad: () => Promise; var _moonlightBrowserFS: MoonlightBrowserFS | undefined; } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index d958a7b..aa0b063 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -56,5 +56,5 @@ async function load() { if (MOONLIGHT_ENV === "web-preload") { load(); } else { - window._moonlightLoad = load; + window._moonlightBrowserLoad = load; }