this repo has no description
1import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types";
2import { readConfig } from "./config";
3import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
4import Logger from "./util/logger";
5
6const logger = new Logger("core/extension");
7
8async function findManifests(dir: string): Promise<string[]> {
9 const ret = [];
10
11 if (await moonlightNodeSandboxed.fs.exists(dir)) {
12 for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) {
13 const path = moonlightNodeSandboxed.fs.join(dir, file);
14 if (file === "manifest.json") {
15 ret.push(path);
16 }
17
18 if (!(await moonlightNodeSandboxed.fs.isFile(path))) {
19 ret.push(...(await findManifests(path)));
20 }
21 }
22 }
23
24 return ret;
25}
26
27async function loadDetectedExtensions(
28 dir: string,
29 type: ExtensionLoadSource,
30 seen: Set<string>
31): Promise<DetectedExtension[]> {
32 const ret: DetectedExtension[] = [];
33
34 const manifests = await findManifests(dir);
35 for (const manifestPath of manifests) {
36 try {
37 if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue;
38 const dir = moonlightNodeSandboxed.fs.dirname(manifestPath);
39
40 const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath));
41 if (seen.has(manifest.id)) {
42 logger.warn(`Duplicate extension found, skipping: ${manifest.id}`);
43 continue;
44 }
45 seen.add(manifest.id);
46
47 const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js");
48 const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js");
49 const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js");
50
51 // if none exist (empty manifest) don't give a shit
52 if (
53 !moonlightNodeSandboxed.fs.exists(webPath) &&
54 !moonlightNodeSandboxed.fs.exists(nodePath) &&
55 !moonlightNodeSandboxed.fs.exists(hostPath)
56 ) {
57 continue;
58 }
59
60 const web = (await moonlightNodeSandboxed.fs.exists(webPath))
61 ? await moonlightNodeSandboxed.fs.readFileString(webPath)
62 : undefined;
63
64 let url: string | undefined = undefined;
65 const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile);
66 if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) {
67 url = await moonlightNodeSandboxed.fs.readFileString(urlPath);
68 }
69
70 const wpModules: Record<string, string> = {};
71 const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules");
72 if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) {
73 const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath);
74
75 for (const wpModuleFile of wpModulesFile) {
76 if (wpModuleFile.endsWith(".js")) {
77 wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString(
78 moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile)
79 );
80 }
81 }
82 }
83
84 const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css");
85
86 ret.push({
87 id: manifest.id,
88 manifest,
89 source: {
90 type,
91 url
92 },
93 scripts: {
94 web,
95 webPath: web != null ? webPath : undefined,
96 webpackModules: wpModules,
97 nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined,
98 hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined,
99 style: (await moonlightNodeSandboxed.fs.exists(stylePath))
100 ? await moonlightNodeSandboxed.fs.readFileString(stylePath)
101 : undefined
102 }
103 });
104 } catch (err) {
105 logger.error(`Failed to load extension from "${manifestPath}":`, err);
106 }
107 }
108
109 return ret;
110}
111
112async function getExtensionsNative(): Promise<DetectedExtension[]> {
113 const config = await readConfig();
114 const res = [];
115 const seen = new Set<string>();
116
117 res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen)));
118
119 res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen)));
120
121 for (const devSearchPath of config.devSearchPaths ?? []) {
122 res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen)));
123 }
124
125 return res;
126}
127
128async function getExtensionsBrowser(): Promise<DetectedExtension[]> {
129 const ret: DetectedExtension[] = [];
130 const seen = new Set<string>();
131
132 const coreExtensionsFs: Record<string, string> = JSON.parse(
133 // @ts-expect-error shut up
134 _moonlight_coreExtensionsStr
135 );
136 const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])));
137
138 for (const ext of coreExtensions) {
139 if (!coreExtensionsFs[`${ext}/index.js`]) continue;
140 const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]);
141 const web = coreExtensionsFs[`${ext}/index.js`];
142
143 const wpModules: Record<string, string> = {};
144 const wpModulesPath = `${ext}/webpackModules`;
145 for (const wpModuleFile of Object.keys(coreExtensionsFs)) {
146 if (wpModuleFile.startsWith(wpModulesPath)) {
147 wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile];
148 }
149 }
150
151 ret.push({
152 id: manifest.id,
153 manifest,
154 source: {
155 type: ExtensionLoadSource.Core
156 },
157 scripts: {
158 web,
159 webpackModules: wpModules,
160 style: coreExtensionsFs[`${ext}/style.css`]
161 }
162 });
163 seen.add(manifest.id);
164 }
165
166 if (await moonlightNodeSandboxed.fs.exists("/extensions")) {
167 ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen)));
168 }
169
170 return ret;
171}
172
173export async function getExtensions(): Promise<DetectedExtension[]> {
174 webPreload: {
175 return moonlightNode.extensions;
176 }
177
178 browser: {
179 return await getExtensionsBrowser();
180 }
181
182 nodeTarget: {
183 return await getExtensionsNative();
184 }
185
186 throw new Error("Called getExtensions() outside of node-preload/web-preload");
187}