+128
packages/core-extensions/src/nativeFixes/host.ts
+128
packages/core-extensions/src/nativeFixes/host.ts
···
1
1
import { app, nativeTheme } from "electron";
2
+
import * as path from "node:path";
3
+
import * as fs from "node:fs/promises";
4
+
import * as fsSync from "node:fs";
5
+
import { parseTarGzip } from "nanotar";
2
6
7
+
const logger = moonlightHost.getLogger("nativeFixes/host");
3
8
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
4
9
5
10
moonlightHost.events.on("window-created", function (browserWindow) {
···
44
49
}
45
50
46
51
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));
52
+
53
+
if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {
54
+
const exePath = app.getPath("exe");
55
+
const appName = path.basename(exePath);
56
+
const targetDir = path.dirname(exePath);
57
+
const { releaseChannel }: { releaseChannel: string } = JSON.parse(
58
+
fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")
59
+
);
60
+
61
+
const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));
62
+
const updater = updaterModule.constructor;
63
+
64
+
async function doUpdate(cb: (percent: number) => void) {
65
+
logger.debug("Extracting to", targetDir);
66
+
67
+
const exists = (path: string) =>
68
+
fs
69
+
.stat(path)
70
+
.then(() => true)
71
+
.catch(() => false);
72
+
73
+
const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;
74
+
const resp = await fetch(url, {
75
+
cache: "no-store"
76
+
});
77
+
78
+
const reader = resp.body!.getReader();
79
+
const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");
80
+
logger.info(`Expecting ${contentLength} bytes for the update`);
81
+
const bytes = new Uint8Array(contentLength);
82
+
let pos = 0;
83
+
let lastPercent = 0;
84
+
85
+
while (true) {
86
+
const { done, value } = await reader.read();
87
+
if (done) {
88
+
break;
89
+
} else {
90
+
bytes.set(value, pos);
91
+
pos += value.length;
92
+
93
+
const newPercent = Math.floor((pos / contentLength) * 100);
94
+
if (lastPercent !== newPercent) {
95
+
lastPercent = newPercent;
96
+
cb(newPercent);
97
+
}
98
+
}
99
+
}
100
+
101
+
const files = await parseTarGzip(bytes);
102
+
103
+
for (const file of files) {
104
+
if (!file.data) continue;
105
+
// @ts-expect-error What do you mean their own types are wrong
106
+
if (file.type !== "file") continue;
107
+
108
+
// Discord update files are inside of a main "Discord(PTB|Canary)" folder
109
+
const filePath = file.name.replace(`${appName}/`, "");
110
+
logger.info("Extracting", filePath);
111
+
112
+
let targetFilePath = path.join(targetDir, filePath);
113
+
if (filePath === "resources/app.asar") {
114
+
// You tried
115
+
targetFilePath = path.join(targetDir, "resources", "_app.asar");
116
+
} else if (filePath === appName) {
117
+
// Can't write over the executable? Just move it! 4head
118
+
if (await exists(targetFilePath)) {
119
+
await fs.rename(targetFilePath, targetFilePath + ".bak");
120
+
await fs.unlink(targetFilePath + ".bak");
121
+
}
122
+
}
123
+
const targetFileDir = path.dirname(targetFilePath);
124
+
125
+
if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });
126
+
await fs.writeFile(targetFilePath, file.data);
127
+
128
+
const mode = file.attrs?.mode;
129
+
if (mode != null) {
130
+
// Not sure why this slice is needed
131
+
await fs.chmod(targetFilePath, mode.slice(-3));
132
+
}
133
+
}
134
+
135
+
logger.debug("Done updating");
136
+
}
137
+
138
+
const realEmit = updater.prototype.emit;
139
+
updater.prototype.emit = function (event: string, ...args: any[]) {
140
+
// Arrow functions don't bind `this` :D
141
+
const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);
142
+
143
+
if (event === "update-manually") {
144
+
const latestVerStr: string = args[0];
145
+
logger.debug("update-manually called, intercepting", latestVerStr);
146
+
call("update-available");
147
+
148
+
(async () => {
149
+
try {
150
+
await doUpdate((progress) => {
151
+
call("update-progress", progress);
152
+
});
153
+
// Copied from the win32 updater
154
+
this.updateVersion = latestVerStr;
155
+
call(
156
+
"update-downloaded",
157
+
{},
158
+
releaseChannel,
159
+
latestVerStr,
160
+
new Date(),
161
+
this.updateUrl,
162
+
this.quitAndInstall.bind(this)
163
+
);
164
+
} catch (e) {
165
+
logger.error("Error updating", e);
166
+
}
167
+
})();
168
+
169
+
return this;
170
+
} else {
171
+
return realEmit.call(this, event, ...args);
172
+
}
173
+
};
174
+
}
+8
-1
packages/core-extensions/src/nativeFixes/manifest.json
+8
-1
packages/core-extensions/src/nativeFixes/manifest.json
···
4
4
"meta": {
5
5
"name": "Native Fixes",
6
6
"tagline": "Various configurable fixes for Discord and Electron",
7
-
"authors": ["Cynosphere", "adryd"],
7
+
"authors": ["Cynosphere", "adryd", "NotNite"],
8
8
"tags": ["fixes"]
9
9
},
10
10
"environment": "desktop",
···
43
43
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
44
44
"type": "boolean",
45
45
"default": true
46
+
},
47
+
"linuxUpdater": {
48
+
"advice": "restart",
49
+
"displayName": "Linux Updater",
50
+
"description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",
51
+
"type": "boolean",
52
+
"default": false
46
53
}
47
54
},
48
55
"apiLevel": 2