185 lines
6.4 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() {
62
// Note: this won't do anything on browser, we should probably disable it
63
// entirely when running in browser.
64
async function downloadStable(): Promise<[ArrayBuffer, string]> {
65
const json = await getStableRelease();
66
const asset = json.assets.find((a) => a.name === artifactName);
67
if (!asset) throw new Error("Artifact not found");
68
69
logger.debug(`Downloading ${asset.browser_download_url}`);
70
const req = await fetch(asset.browser_download_url, {
71
cache: "no-store",
72
headers: {
73
"User-Agent": userAgent
74
}
75
});
76
77
return [await req.arrayBuffer(), json.name];
78
}
79
80
async function downloadNightly(): Promise<[ArrayBuffer, string]> {
81
logger.debug(`Downloading ${nightlyZipUrl}`);
82
const zipReq = await fetch(nightlyZipUrl, {
83
cache: "no-store",
84
headers: {
85
"User-Agent": userAgent
86
}
87
});
88
89
const refReq = await fetch(nightlyRefUrl, {
90
cache: "no-store",
91
headers: {
92
"User-Agent": userAgent
93
}
94
});
95
const ref = (await refReq.text()).split("\n")[0];
96
97
return [await zipReq.arrayBuffer(), ref];
98
}
99
100
const [tar, ref] =
101
moonlightGlobal.branch === MoonlightBranch.STABLE
102
? await downloadStable()
103
: moonlightGlobal.branch === MoonlightBranch.NIGHTLY
104
? await downloadNightly()
105
: [null, null];
106
107
if (!tar || !ref) return;
108
109
const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir);
110
if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
111
await moonlightNodeSandboxed.fs.mkdir(dist);
112
113
logger.debug("Extracting update");
114
const files = await parseTarGzip(tar);
115
for (const file of files) {
116
if (!file.data) continue;
117
// @ts-expect-error What do you mean their own types are wrong
118
if (file.type !== "file") continue;
119
120
const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
121
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
122
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
123
await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
124
}
125
126
logger.debug("Writing version file:", ref);
127
const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile);
128
await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
129
130
logger.debug("Update extracted");
131
},
132
133
async fetchRepositories(repos) {
134
const ret: Record<string, RepositoryManifest[]> = {};
135
136
for (const repo of repos) {
137
try {
138
const req = await fetch(repo, {
139
cache: "no-store",
140
headers: {
141
"User-Agent": userAgent
142
}
143
});
144
const json = await req.json();
145
ret[repo] = json;
146
} catch (e) {
147
logger.error(`Error fetching repository ${repo}`, e);
148
}
149
}
150
151
return ret;
152
},
153
154
async installExtension(manifest, url, repo) {
155
const req = await fetch(url, {
156
cache: "no-store",
157
headers: {
158
"User-Agent": userAgent
159
}
160
});
161
162
const dir = moonlightGlobal.getExtensionDir(manifest.id);
163
// remake it in case of updates
164
if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
165
await moonlightNodeSandboxed.fs.mkdir(dir);
166
167
const buffer = await req.arrayBuffer();
168
const files = extractAsar(buffer);
169
for (const [file, buf] of Object.entries(files)) {
170
const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
171
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
172
173
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
174
await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
175
}
176
177
await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
178
},
179
180
async deleteExtension(id) {
181
const dir = moonlightGlobal.getExtensionDir(id);
182
await moonlightNodeSandboxed.fs.rmdir(dir);
183
}
184
};
185
}
186