Adventures in Hacking Electron Apps
Tags: electron, hacking, commandline, javascript
Published: 2019-03-01 04:15:03

---
title: Adventures in Hacking Electron Apps
published: true
description: Have you ever wanted to change some annoying behavior of an app running on your computer? While this is often impossible, apps written in Electron can be hacked to modify their source code. Here's my journey in figuring out how to do so. Follow along, and learn about electron, hacking and some useful command-line tools!
tags: electron, hacking, command line, javascript
---

*Disclaimer: Hacking apps is often against the Terms of Service. This article is purely theoretical and not an endorsement of the practice. Always Hack Responsibly.*

Have you ever been *extremely annoyed* by a certain aspect of an app or website? 

If you're anything like me, the answer is probably yes. If you're a lot like me, the answer is "yes, often."

On the web, I can easily solve most grievances thanks to [extensions that allow you to inject custom CSS and Javascript on any website](https://chrome.google.com/webstore/detail/user-javascript-and-css/nbhcbdghjpllgmfilhnhkllmkecfmpld). With desktop apps, however, I usually have to live with my pain, hoping the developers will one day see the light and decide to fix the problem (or will finally have the time to prioritize doing so).

Unless the app is an [Electron](https://electronjs.org/) app.

I don't remember how I learned about this possibility—sometimes, there's no stronger driving force than the need to fix bad design.

In this article, I'm going to talk about how I changed Discord's app code to solve one of my ([and](https://support.discordapp.com/hc/en-us/community/posts/360037394592-Reduce-horizontal-size-of-window-or-pop-out-chats) [other's](https://support.discordapp.com/hc/en-us/community/posts/360029464911-Make-minimum-window-size-an-optional-feature-)) biggest grievances: its huge minimum window size.

![Discord's default minimum window size](https://thepracticaldev.s3.amazonaws.com/i/l4j1d4o62f83tezw24im.png)
<figcaption>Discord's *huge* minimum window size (940px).</figcaption>

Let's be clear, though: this post—just like any worthy software endeavor—is about the *journey* rather than the solution. So follow along my adventure, see how I figured out the needed steps, and learn something about the command line, electron apps and hacking!

*Note: while I use macOS High Sierra, this process can be similarly replicated on other operating systems.*

# Unraveling Discord's Secrets

Initially, I wanted to learn as much as I could about the Discord application. MacOS has a [UI mechanism to explore application contents](https://apple.stackexchange.com/questions/15658/how-can-i-open-a-pkg-file-manually/15665#15665), which is a good "step zero." Manual exploration only goes so far, though, and soon I turned to the command line. 

## Step 1: What does Discord's Process Look Like?

To answer my own question, I ran the following command:

```shell
ps x | grep Discord
```

In short, `ps` lists all the processes running, `x` includes the ones not attached to a shell (e.g. those started by clicking on application icons), and piping this output (`|`) to the `grep` command displays only the ones featuring the string `Discord`. You can learn more at [explainshell.com](https://explainshell.com/explain?cmd=ps+x+%7C+grep+Discord).

Here's the output, edited for readability:

```shell
1927   ??  S      0:00.08 /Applications/Discord.app/Contents/Frameworks/Electron Framework.framework/Resources/crashpad_handler 
    --no-rate-limit --no-upload-gzip 
    --database=/var/folders/sm/4v5p46v175d3x94qp56r37340000gn/T/Discord Crashes 
    --metrics-dir=/var/folders/sm/4v5p46v175d3x94qp56r37340000gn/T/Discord Crashes 
    --url=http://crash.discordapp.com:1127/post 
    --handshake-fd=73
```
<figcaption>Process #1927</figcaption>
```shell
1928   ??  R     34:58.78 /Applications/Discord.app/Contents/Frameworks/Discord Helper.app/Contents/MacOS/Discord Helper 
    --type=renderer --no-sandbox --autoplay-policy=no-user-gesture-required 
    --force-color-profile=srgb --enable-features=SharedArrayBuffer 
    --disable-features=MacV2Sandbox --service-pipe-token=5494336596696404231 
    --lang=en-US 
    --app-path=/Applications/Discord.app/Contents/Resources/app.asar 
    --node-integration=false --webview-tag=false --no-sandbox 
    --preload=/Users/essential_randomness/Library/Application Support/discord/0.0.254/modules/discord_desktop_core/core.asar/app/mainScreenPreload.js 
    --background-color=#2f3136 --num-raster-threads=2 --enable-zero-copy 
    --enable-gpu-memory-buffer-compositor-resources 
    --enable-main-frame-before-activation 
    --service-request-channel-token=5494336596696404231 --renderer-client-id=6
```
<figcaption>Process #1928</figcaption>

The first process (#1927) seemed to be related to reporting app crashes. I assumed this because of `.../crashpad_handler` in the app path, and the `url` flag pointing to `http://crash.discordapp.com:1127/post` (which is probably the server endpoint crash reports are communicated to).

The second process (#1928) was more promising. In particular I found the value of the `app-path` variable (`/Applications/Discord.app/Contents/Resources/app.asar`) worth exploring.

## Step 2: Extracting the App Code.

The `.asar` extension of the file intrigued me. After a quick google search for "asar files", I found a [GitHub repo](https://github.com/electron/asar) explaining the format:

> Asar is a simple extensive archive format, it works like tar that concatenates all files together without compression, while having random access support.

Luckily, the repository contained information on how to install the asar command- line utility (`npm install asar`) and how to extract archived files from asar files. My next step was both obvious and easy. 

Before making any changes, however, I decided to backup the original archive:

```shell
# Backup original file in case of emergency
cd /Applications/Discord.app/Contents/Resources/
cp app.asar app_safe_copy.asar

# Extract app.asar to a folder named "unpacked"
asar extract app.asar unpacked/
```

After cracking this metaphorical treasure chest open, it was time to list (`ls`) its content!

```shell
cd unpacked
ls
# output:
> app_bootstrap common        node_modules  package.json

ls app_bootstrap/
# output:
> Constants.js       bootstrap.js       ...       appSettings.js       ...
```

The familiar `node_modules/` + `package.json` combination pointed to the archive being an npm package, bundling together a bunch of JavaScript files and other things like images. This was clearly important code! Even better, the code was not compiled or encrypted in any way. 

For the first time I thought that (maybe) I could really pull this off! 

## Step 3: What is This Code?

I explored the JS files by opening them in VsCode to get some insight on the app's structure. This was interesting, but very slow.

To be faster, I decided to bet on a simple assumption: any file controlling the window width would have had to contain the string "width" itself!

Also, I could exclude the node_modules folder from my search because npm packages reserve this directory for external libraries.

```shell
# Find all the files containing the string width in the current folder,
# but exclude the ones in the node_modules one.
grep -iRl "width" ./ | grep -v node_modules
# Output:
> .//app_bootstrap/splash/index.js
> .//app_bootstrap/splash/variables.json
> .//app_bootstrap/splashScreen.js
```

[In depth explanation of the command.](https://explainshell.com/explain?cmd=grep+-iRl+%22width%22+.%2F+%7C+grep+-v+node_modules)

This output was disappointing: the files were clearly related to the splash screen, which wasn't what I was looking to change. I tried going up in the top-level Discord folder (`/Applications/Discord.app/`) and ran the command again, but the output wasn't much different.

It seemed my luck had ran out.

## Step 4: Sometimes Stepping Back is a Step Forward

Rather than despair, I decided to go back to process #1928. The `preload` flag held another interesting path, in a completely different location than the previous one: `/Users/essential_randomness/Library/Application Support/discord/0.0.254/modules/discord_desktop_core/core.asar/app/mainScreenPreload.js`.

It was time for another adventure!

```shell
# Once again, I searched for files containing the string "width".
cd /Users/essential_randomness/Library/Application\ Support/discord/
grep -iRl "width" ./ | grep -v node_modules
# Output
> .//Preferences
> ...
> .//settings.json
> ...
> .//0.0.254/modules/discord_desktop_core/core.asar
```
<figcaption>This was when I found out you can grep the content of asar files without unpacking them first. Forgetting steps can lead to interesting discoveries.</figcaption>

This search yielded quite a number of files, so I decided to try to narrow it down further. Since I was looking to change the minimum width, I figured any related variable name would be called either minWidth or min_width. After all, we all take code readability seriously, don't we?

```shell
grep -iRl "min_width" ./ | grep -v node_modules
# Output
> .//0.0.254/modules/discord_desktop_core/core.asar

grep -iRl "minWidth" ./ | grep -v node_modules
# Output
> .//0.0.254/modules/discord_desktop_core/core.asar
> .//0.0.254/modules/discord_voice/discord_voice.node
```

`core.asar` looked really promising! Once again I extracted it and searched for the right file:

```shell
cd 0.0.254/modules/discord_desktop_core
cp core.asar core_safe_copy.asar
asar extract core.asar core_unpacked
cd core_unpacked

# Trying min_width first, as the value is likely a constant.
# Constants use, by many code conventions, a capitalized style (i.e. "MIN_WIDTH").
grep -iRl "min_width" ./ | grep -v node_modules
# Output:
> .//app/mainScreen.js
```

Could `.//app/mainScreen.js` finally be The One? I immediately opened it, searched for "min_width" and...

```javascript
const MIN_WIDTH = settings.get('MIN_WIDTH', 940);
const MIN_HEIGHT = settings.get('MIN_HEIGHT', 500);
```
<figcaption> ...JACKPOT!</figcaption>

## Step 5: The Moment of Truth

I knew I had to be onto something. With no clue about whether it would work (but tons of faith) I edited the code:

```javascript

const MIN_WIDTH = settings.get('MIN_WIDTH', 0);
const MIN_HEIGHT = settings.get('MIN_HEIGHT', 0);
```

Now all I needed to do was repack the changed asar file. Again, I made sure to create a backup of core.asar (`cp core.asar core_safe_copy.asar`) before going further. Bricking Discord completely was a real possibility here!

With huge trepidation, I ran the final step:

```shell
# Remove the original app file and swap it with our edited code, repacked.
rm core.asar
asar pack core_unpacked core.asar
```

At this point I restarted Discord, hoping the changes would take effect. I placed my cursor at the app border, started dragging and... IT WORKED!

![Discord with minimum window size removed](
https://thepracticaldev.s3.amazonaws.com/i/v0ejjedxsokhcy7c4eru.gif)
<figcaption>Discord with minimum window size removed</figcaption>

### Side Quest: "I Also Like to Live Dangerously"

At this point, I had still one last curiosity. What would have happened if I had messed up while modifying the code?

I re-extracted the asar file, purposely inserted an invalid JavaScript statement, repacked it and tried to run the app. Unsurprisingly, I got an error!

This hammered down the importance of back ups. Since I had wisely created a copy of core.asar, I simply put the original code back in its place and the error was gone.

# Conclusion

As a final warning, modifying code this way is likely against most Terms of Service (cue the usual Great Power => Great Responsibility speech). 

Another aspect of code hacking to think about is side effects: Discord is not optimized to be displayed at lower sizes and the UI can be wonky. Since Electron apps use Chromium as a fronted, I modified the UI myself through the Developer Tools console (which Discord kindly makes available under "view > developer > developer tools").

Please remember that a very valid reason why developers don't want their code modified is that it can cause unexpected bugs in the app itself. If you choose to run a custom version of any code, don't go filing bugs unless you can reproduce them in the original app!

# A Funny Epilogue

After going through all this, I found out there's an [easier way to change Discord's window size](http://www2.powertrip.net/index.php?threads/resizing-discord-window.382/) that does not require modifying the source code. 

But, you know, where would the fun be in that?

Happy Hacking!