Skip to content

Commit

Permalink
nativeFixes: Linux updater
Browse files Browse the repository at this point in the history
  • Loading branch information
NotNite committed Jan 20, 2025
1 parent 06bba0c commit 77b1acd
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 1 deletion.
128 changes: 128 additions & 0 deletions packages/core-extensions/src/nativeFixes/host.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { app, nativeTheme } from "electron";
import * as path from "node:path";
import * as fs from "node:fs/promises";
import * as fsSync from "node:fs";
import { parseTarGzip } from "nanotar";

const logger = moonlightHost.getLogger("nativeFixes/host");
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");

moonlightHost.events.on("window-created", function (browserWindow) {
Expand Down Expand Up @@ -44,3 +49,126 @@ if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) &&
}

app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));

if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {
const exePath = app.getPath("exe");
const appName = path.basename(exePath);
const targetDir = path.dirname(exePath);
const { releaseChannel }: { releaseChannel: string } = JSON.parse(
fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")
);

const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));
const updater = updaterModule.constructor;

async function doUpdate(cb: (percent: number) => void) {
logger.debug("Extracting to", targetDir);

const exists = (path: string) =>
fs
.stat(path)
.then(() => true)
.catch(() => false);

const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;
const resp = await fetch(url, {
cache: "no-store"
});

const reader = resp.body!.getReader();
const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");
logger.info(`Expecting ${contentLength} bytes for the update`);
const bytes = new Uint8Array(contentLength);
let pos = 0;
let lastPercent = 0;

while (true) {
const { done, value } = await reader.read();
if (done) {
break;
} else {
bytes.set(value, pos);
pos += value.length;

const newPercent = Math.floor((pos / contentLength) * 100);
if (lastPercent !== newPercent) {
lastPercent = newPercent;
cb(newPercent);
}
}
}

const files = await parseTarGzip(bytes);

for (const file of files) {
if (!file.data) continue;
// @ts-expect-error What do you mean their own types are wrong
if (file.type !== "file") continue;

// Discord update files are inside of a main "Discord(PTB|Canary)" folder
const filePath = file.name.replace(`${appName}/`, "");
logger.info("Extracting", filePath);

let targetFilePath = path.join(targetDir, filePath);
if (filePath === "resources/app.asar") {
// You tried
targetFilePath = path.join(targetDir, "resources", "_app.asar");
} else if (filePath === appName) {
// Can't write over the executable? Just move it! 4head
if (await exists(targetFilePath)) {
await fs.rename(targetFilePath, targetFilePath + ".bak");
await fs.unlink(targetFilePath + ".bak");
}
}
const targetFileDir = path.dirname(targetFilePath);

if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });
await fs.writeFile(targetFilePath, file.data);

const mode = file.attrs?.mode;
if (mode != null) {
// Not sure why this slice is needed
await fs.chmod(targetFilePath, mode.slice(-3));
}
}

logger.debug("Done updating");
}

const realEmit = updater.prototype.emit;
updater.prototype.emit = function (event: string, ...args: any[]) {
// Arrow functions don't bind `this` :D
const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);

if (event === "update-manually") {
const latestVerStr: string = args[0];
logger.debug("update-manually called, intercepting", latestVerStr);
call("update-available");

(async () => {
try {
await doUpdate((progress) => {
call("update-progress", progress);
});
// Copied from the win32 updater
this.updateVersion = latestVerStr;
call(
"update-downloaded",
{},
releaseChannel,
latestVerStr,
new Date(),
this.updateUrl,
this.quitAndInstall.bind(this)
);
} catch (e) {
logger.error("Error updating", e);
}
})();

return this;
} else {
return realEmit.call(this, event, ...args);
}
};
}
9 changes: 8 additions & 1 deletion packages/core-extensions/src/nativeFixes/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"meta": {
"name": "Native Fixes",
"tagline": "Various configurable fixes for Discord and Electron",
"authors": ["Cynosphere", "adryd"],
"authors": ["Cynosphere", "adryd", "NotNite"],
"tags": ["fixes"]
},
"environment": "desktop",
Expand Down Expand Up @@ -43,6 +43,13 @@
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
"type": "boolean",
"default": true
},
"linuxUpdater": {
"advice": "restart",
"displayName": "Linux Updater",
"description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",
"type": "boolean",
"default": false
}
},
"apiLevel": 2
Expand Down

0 comments on commit 77b1acd

Please sign in to comment.