Skip to content

Blog

moonlight 1.2: Linux, browsers, installers, and more

Right after releasing moonlight API v2 we knew we wanted to work on more moonlight stuff. What was originally planned as a “1.1.1” kept growing in size until it was large enough that we decided to upgrade to a “1.2.0”. We ended up making over 100 commits in one week!

With this release, the moonlight API version still stays at 2. Assuming your extension was up to date and functioning before, it should continue to be fine now.

Let’s talk about all the new changes!

Welcome to the new website

What you’re reading right now wasn’t what it was like before. The moonlight website and documentation is now powered by Starlight, an Astro-based documentation site with an incredibly fitting name. We migrated from Docusaurus since it’s more modern, looks nicer, and builds faster. All the content is still the same, but some links might be broken.

We use the starlight-blog plugin for this blog. We even got a new RSS feed with it!

Introducing rocketship, a custom Discord Linux build

Discord on Linux has been a notably bad experience for a while now. Several clients exist to make it an easier experience, but they don’t support moonlight. So we made our own!

One of the biggest pain points is screensharing. The Discord client ships with two methods for voice connections: using WebRTC for browsers, and using their native Rust/C++ engine for desktop. The native engine support on Linux is quite bad, but WebRTC support can be better in some situations. Unfortunately, Discord’s Electron build disables the required APIs for using the WebRTC engine on desktop.

While you can just use stock Electron for a custom client, Discord’s modified Electron is desirable for its various fixes. We were able to fork Discord’s fork and remove the patch we didn’t want, keeping the others. After building Electron, we uploaded it as a GitHub release and wrote rocketship, a script to install it into Discord. rocketship downloads the official Discord Linux client, keeping its .asar and desktop entry, but removes the Electron binary and replaces it with our own.

rocketship is thus less of a packaged Discord distribution and more of an installer. rocketship can be used without moonlight, but you don’t gain any of its benefits without using moonlight. The moonlight rocketship extension can force Discord to use the WebRTC APIs and then use venmic to connect audio.

rocketship is still very experimental, but we encourage Linux users to try it out and report any issues!

A new installer, and a new CLI

The moonlight installer was rewritten, along with a new CLI project. Instead of using Tauri, we moved to egui for the UI.

A screenshot of the moonlight installer

Our motivation for moving from Tauri is that it’s rough to use. Linux support is flakey (we had many issues on Wayland), packaging for Windows required an installer for the installer because of WebViews, and it was just extremely heavy for a tool that needed to download some files and extract them somewhere. With egui, the moonlight installer is a single self-contained ~5 MB .exe (minus Visual C++ Redistributables).

In the process of rewriting the installer, we had to write publishing scripts for macOS and Linux. This turned out to be in a very sorry state, as most tools to create .dmg and .AppImage files are kinda bad. We ended up having to abandon Linux AppImage support because of glibc issues, and wrote our own macOS bundle script. Linux users can build the GUI installer from source, but we encourage using the CLI:

Terminal window
./rocketship.sh -b stable
moonlight-cli install stable
moonlight-cli patch ~/.local/share/Discord/Discord

A very special thank you to Emma/InvoxiPlayGames for PRing macOS CI scripts, and Eva for the original installer & macOS support. PRs for Linux AppImage support will be accepted in the future, if anyone is willing to help.

Moonbase UX improvements, and updating moonlight from Discord

Moonbase now has a notice that displays at the top of the screen when updates are available. Moonbase can check for updates to moonlight, as well as updates for the extensions you have installed.

A screenshot of a banner at the top of the Discord client, showing that moonlight can be updated and 1 extension update is available

When in Moonbase, this prompt will show at the top of the page, allowing you to update moonlight with a single click from inside Discord:

A screenshot of a notice in Moonbase, with a button to update moonlight

Moonbase can also prompt you when you install an extension with a missing dependency. If an extension is present in multiple sources, you can pick which one to install. This makes it easier to make your own libraries on custom repositories and make sure they’re installed with your extension.

A screenshot of the dependency prompt in Moonbase after installing an extension

While making these improvements, we introduced a new notice library for showing custom messages, with as many buttons as you like and custom styling. It even supports multiple notices being queued!

moonlight now runs in the browser

moonlight now has experimental support to run as a browser extension. We used ZenFS to create a local filesystem in the browser, allowing you to install and load extensions like normal. This was planned since May(!), but it needed some final touches for extension installation and core loading. The browser extension works both in Chrome and Firefox, with support for Manifest V2 and Manifest V3.

The browser extension has to block Discord from loading so we can load first, given that moonlight is asynchronous and we can’t easily tell the page to wait for us. To solve this, the extension blocks Discord’s JavaScript files from loading, loads moonlight, and then unblocks the scripts and reloads them. Clever!

Squashing bugs and making new ones

We made a lot of fixes and improvements this release, with a focus on Moonbase, the extension manager GUI for moonlight. Here’s what we were up to:

Bringing in a new core developer

We welcomed redstonekasi to the core developer team. Kasi has done exceptional work improving Moonbase and maintaining moonlight through the year, along with making lots of extensions in the official repository. The title of “core developer” doesn’t change much with their access, but we want to recognize their efforts. Thanks!

That’s all for now

That’s everything we’ve got for moonlight 1.2. Development might slow down for a bit, as a lot of work has gone into maintenance & experimenting with new projects, so what remains is mostly extension work. As always, we encourage developers to try making extensions and submitting them to the official repository if they wish - more extensions can always help.

Thanks for using moonlight!

moonlight API v2, mappings, and more

Been a while, eh? The last post we wrote for moonlight was when we first introduced it. Sounds like it’s time to change that!

moonlight development was stalled for a while this year, particularly due to lack of motivation. Less people than expected tinkering with moonlight gave us less reason to update it, and so it fell behind with Discord updates, eventually breaking catastrophically for several months. Thanks to redstonekasi for submitting pull requests for fixing a lot of things while none of us had the energy to.

About a week ago, Ari had the idea of making a centralized repository for Discord client mappings. This short conversation would lead to one of the greatest nerdsnipes in moonlight history.

With the new features we’re rolling out, this means new concepts of an “API level”. Extensions that do not meet the provided API level will not load. If you don’t care about the shiny new toys, jump here for more info on that.

LunAST: AST-based mapping and patching

The concept of centralized mappings started with LunAST (Lunar + AST), a library for manipulating Webpack modules with various ESTree-related tools. LunAST enables you to patch and traverse modules using an AST, which is much more flexible than the current text-based patching with RegEx/strings.

Here’s the first LunAST patch we ever wrote, as an idea for how it works. This makes it say “balls” when you click on an image preview (very professional, I know):

import type { AST } from "@moonlight-mod/types";
moonlight.lunast.register({
name: "ImagePreview",
find: ".Messages.OPEN_IN_BROWSER",
process({ id, ast, lunast, markDirty }) {
const getters = lunast.utils.getPropertyGetters(ast);
const replacement = lunast.utils
.magicAST(`return require("common_react").createElement(
"div",
{
style: {
color: "white",
},
},
"balls"
)`)!;
for (const data of Object.values(getters)) {
if (!lunast.utils.is.identifier(data.expression)) continue;
const node = data.scope.getOwnBinding(data.expression.name);
if (!node) continue;
const body = node.path.get<AST.BlockStatement>("body");
body.replaceWith(replacement);
}
markDirty();
return true;
}
});

This is a very powerful tool, but we aren’t sure how well it’ll work for extension developers just yet. If you have a particularly complicated patch in your extension codebase, see if LunAST can help you. Some notes, though:

  • AST parsing is expensive! Use find when possible to filter for modules to parse.
  • AST patching might break other text-based patches. If you encounter any weirdness, let us know.
  • Text-based patching is not going away, and you should not use AST patching everywhere. This is purely a tool for the harder stuff.

moonmap: dynamic remapping of Webpack modules

moonmap allows you to find a module, create a proper name for it, and create named exports from minified variable names. This feature was originally in LunAST, but we decided to expand on it and bring it into its own library. Here’s a snippet from the mappings project (which we’ll get into later):

const name = "discord/utils/HTTPUtils";
moonlight.moonmap.register({
name,
find: '.set("X-Audit-Log-Reason",',
process({ id, moonmap }) {
moonmap.addModule(id, name);
moonmap.addExport(name, "HTTP", {
type: ModuleExportType.Key,
find: "patch"
});
return true;
}
});

You can then just spacepack.require("discord/utils/HTTPUtils").HTTP, and it just works! Magic:tm:.

mappings: client mod agnostic Discord mappings

mappings combines moonmap and LunAST into one project to map out the Discord client. This is an idea that was tested in HH3, and we believe that it’s stable by now.

The biggest feature about the mappings is that they aren’t locked into the moonlight patcher system. Any client mod that can implement moonmap and LunAST into their patching system can use the mappings repository with no extra effort. We hope this can save some duplicated effort across the client modding community.

import load from "@moonlight-mod/mappings";
load(moonmap, lunast);
// later, after the modules finish initializing
const Dispatcher = require("discord/Dispatcher").default;

There’s still a lot of work to be done for typing the untyped modules, and for adding new modules. We migrated a majority of types in the Common extension into this library, and most of the things in Common have been removed.

What a version bump means for you

As a user

All extensions you are currently using (minus the extensions built into moonlight) will stop working. The developers of those extensions will need to update them, and if those extensions are on the official repository, they will need to be resubmitted and reviewed.

As an extension developer

Your extensions will need to be updated. See this new page in the documentation.

As another client mod developer

All of the libraries mentioned above can be used by your own code now. Have fun!

The future of moonlight

moonlight, like the PlayStation 5, is pointless when there’s nothing to install onto it. As with last time, we encourage developers to try making extensions. Let us know if there’s anything we can improve, and submit your extension to the official repository if you’d like.

We don’t consider this a “moonlight 2.0” as much as “moonlight API version 2”. There’s no groundbreaking rewrite going on here, just some new libraries to play with.

(Re)introducing moonlight: Yet another Discord mod

A few days ago, we released a new Discord client mod called moonlight. moonlight is the end result of our ideas and experiences from Discord client modding, influenced by several years of working on other client modding projects. We invite developers to try out moonlight and report feedback on what we can improve.

Yet another Discord mod

Tens if not hundreds of Discord desktop client mods have come and gone, so why make another? Simple; the freedom to innovate and experiment. With moonlight, we’re free to do what we want and experiment with the ideas that we’ve had for years without any limitations. Creating moonlight has offered a fresh slate to build our own systems and play with what we like most, and allows us to try out new things with zero consequence.

moonlight functions off of Webpack patching (really Rspack, but whatever) - extension developers can modify minified Discord code through string/RegExp replacements, swap entire Webpack modules out, and insert their own Webpack modules. With moonlight’s Webpack module loading, you can import and export in a file like normal, which will be transformed into a Webpack module. This system of patching was inspired and iterated through several other client mods we’ve used and worked on; notably yelm, EndPwn, and HH3. This patching system is also seen in some modern client mods like Vencord.

Despite moonlight being inspired from some other client mods, we don’t intend for it to seriously compete with the current playing field; it will mostly be an experimental playground for the foreseeable future. Don’t let this stop you from trying it, though! We’ll do our best to maintain moonlight and establish a stable API as soon as possible.

Getting started with moonlight

For installing moonlight, refer to manual instructions or our experimental GUI installer. For extension development, see this documentation entry. The documentation tab has lots of information on setting up development environments for extensions and moonlight itself.

If you feel like anything’s unclear, please let us know in our Discord server or make a pull request to this website.

The stable and not-so-stable

moonlight has a lot of features we’re currently working on that may change at any moment. At the current moment, its API is under heavy development and breaking changes will be made. With the help of other developers to try it out and contribute ideas and feedback, we aim to stabilize it soon.

moonlight functions under two release channels: a stable channel (published to GitHub releases on every version), and a nightly channel (published to GitHub Pages through GitHub Actions on every commit to the develop branch). In this experimental development period, we suggest developers use nightly when possible (either through the “Nightly” dropdown in the moonlight installer, or by checking out the develop branch in Git).

Embracing the freedom to develop

We care about the freedom to do what you want with extension development. moonlight is open source under the LGPL-3.0-or-later license.

Extensions can be loaded from disk, the official extension repository, or remote extension repositories. Anyone can create their own extension repository and publish their own extensions; while submitting to the official extension repository is encouraged, it isn’t required. Custom extension repositories are a single .json file that contain URLs to .asar files. The sample extension publishes the extensions you write as an extension repository to GitHub Pages - everything you need to create a custom extension repository is done for you automatically.

moonlight also comes with a set of core extensions that come pre-installed into the Discord client (but you can disable them). Some of them are libraries (like Common) and some of them are simple utility (like Moonbase, the settings GUI). These libraries have types, which can be utilized when using the Webpack require function, or the special import prefix in ESM Webpack modules. Using third party libraries is as simple as adding a .d.ts to your project.

What’s with HH3?

You may have seen some mentions of an “HH3” on the landing page or this blog post. This is a private clientmod we work on, currently on its third iteration (hence the name). HH3 functions very similarly to moonlight, having its own Webpack module patching and insertion system. Some parts of moonlight are based off of HH3 or its extensions; we have been cleared for approval to use these parts or wrote them ourselves. For the (very likely small amount of) HH3 users reading this post, let’s make it clear that HH3 is not dead, and moonlight is not a successor or competitor to HH3. We will continue to use and maintain both.

Extended thanks and acknowledgment to Mary and twilight sparkle, who’ve helped build and shape HH3 and what came before it.

What’s next for moonlight

Our roadmap for what we want to do with moonlight is powered by the community. We want to offer developers as much freedom as possible to send feedback and pitch ideas for what we can do. Some guidelines, though:

  • Please submit your extensions to the official repository (even if the merge times are upsetting), because having them in one place will help coordinate things.
  • If you’re going to be developing libraries, please make pull requests to the moonlight core extensions! Your code can benefit everyone.
  • Please report bugs on GitHub issues, or if you must, in the Discord server.
  • Please don’t share this with all of your friends and go “OMG NEW CLIENT MOD”. Having a bunch of end users trying to use a client mod that’s an active work in progress is a massive headache.

Thanks for reading, and hopefully you enjoy moonlight! <3