187 lines
6.5 kB
1
import { MoonlightBranch } from "@moonlight-mod/types";
2
import type { MoonbaseNatives, RepositoryManifest } from "./types";
3
import extractAsar from "@moonlight-mod/core/asar";
4
import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants";
5
import { parseTarGzip } from "nanotar";
6
7
const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode;
8
9
const githubRepo = "moonlight-mod/moonlight";
10
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
11
const artifactName = "dist.tar.gz";
12
13
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
14
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
15
16
export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`;
17
18
async function getStableRelease(): Promise<{
19
name: string;
20
assets: {
21
name: string;
22
browser_download_url: string;
23
}[];
24
}> {
25
const req = await fetch(githubApiUrl, {
26
cache: "no-store",
27
headers: {
28
"User-Agent": userAgent
29
}
30
});
31
return await req.json();
32
}
33
34
export default function getNatives(): MoonbaseNatives {
35
const logger = moonlightGlobal.getLogger("moonbase/natives");
36
37
return {
38
async checkForMoonlightUpdate() {
39
try {
40
if (moonlightGlobal.branch === MoonlightBranch.STABLE) {
41
const json = await getStableRelease();
42
return json.name !== moonlightGlobal.version ? json.name : null;
43
} else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) {
44
const req = await fetch(nightlyRefUrl, {
45
cache: "no-store",
46
headers: {
47
"User-Agent": userAgent
48
}
49
});
50
const ref = (await req.text()).split("\n")[0];
51
return ref !== moonlightGlobal.version ? ref : null;
52
}
53
54
return null;
55
} catch (e) {
56
logger.error("Error checking for moonlight update", e);
57
return null;
58
}
59
},
60
61
async updateMoonlight(overrideBranch?: MoonlightBranch) {
62
const branch = overrideBranch ?? moonlightGlobal.branch;
63
64
// Note: this won't do anything on browser, we should probably disable it
65
// entirely when running in browser.
66
async function downloadStable(): Promise<[ArrayBuffer, string]> {
67
const json = await getStableRelease();
68
const asset = json.assets.find((a) => a.name === artifactName);
69
if (!asset) throw new Error("Artifact not found");
70
71
logger.debug(`Downloading ${asset.browser_download_url}`);
72
const req = await fetch(asset.browser_download_url, {
73
cache: "no-store",
74
headers: {
75
"User-Agent": userAgent
76
}
77
});
78
79
return [await req.arrayBuffer(), json.name];
80
}
81
82
async function downloadNightly(): Promise<[ArrayBuffer, string]> {
83
logger.debug(`Downloading ${nightlyZipUrl}`);
84
const zipReq = await fetch(nightlyZipUrl, {
85
cache: "no-store",
86
headers: {
87
"User-Agent": userAgent
88
}
89
});
90
91
const refReq = await fetch(nightlyRefUrl, {
92
cache: "no-store",
93
headers: {
94
"User-Agent": userAgent
95
}
96
});
97
const ref = (await refReq.text()).split("\n")[0];
98
99
return [await zipReq.arrayBuffer(), ref];
100
}
101
102
const [tar, ref] =
103
branch === MoonlightBranch.STABLE
104
? await downloadStable()
105
: branch === MoonlightBranch.NIGHTLY
106
? await downloadNightly()
107
: [null, null];
108
109
if (!tar || !ref) return;
110
111
const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir);
112
if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
113
await moonlightNodeSandboxed.fs.mkdir(dist);
114
115
logger.debug("Extracting update");
116
const files = await parseTarGzip(tar);
117
for (const file of files) {
118
if (!file.data) continue;
119
// @ts-expect-error What do you mean their own types are wrong
120
if (file.type !== "file") continue;
121
122
const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
123
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
124
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
125
await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
126
}
127
128
logger.debug("Writing version file:", ref);
129
const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile);
130
await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
131
132
logger.debug("Update extracted");
133
},
134
135
async fetchRepositories(repos) {
136
const ret: Record<string, RepositoryManifest[]> = {};
137
138
for (const repo of repos) {
139
try {
140
const req = await fetch(repo, {
141
cache: "no-store",
142
headers: {
143
"User-Agent": userAgent
144
}
145
});
146
const json = await req.json();
147
ret[repo] = json;
148
} catch (e) {
149
logger.error(`Error fetching repository ${repo}`, e);
150
}
151
}
152
153
return ret;
154
},
155
156
async installExtension(manifest, url, repo) {
157
const req = await fetch(url, {
158
cache: "no-store",
159
headers: {
160
"User-Agent": userAgent
161
}
162
});
163
164
const dir = moonlightGlobal.getExtensionDir(manifest.id);
165
// remake it in case of updates
166
if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
167
await moonlightNodeSandboxed.fs.mkdir(dir);
168
169
const buffer = await req.arrayBuffer();
170
const files = extractAsar(buffer);
171
for (const [file, buf] of Object.entries(files)) {
172
const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
173
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
174
175
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
176
await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
177
}
178
179
await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
180
},
181
182
async deleteExtension(id) {
183
const dir = moonlightGlobal.getExtensionDir(id);
184
await moonlightNodeSandboxed.fs.rmdir(dir);
185
}
186
};
187
}
188