Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ src/package-lock.json
*.asar
_*
miniSrc/
tmp/
AGENTS.md
.venv/

*.crswap # crostini tmp files
*.crswap # crostini tmp files
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@
## [Install Guide](https://github.com/GooseMod/OpenAsar/wiki/Install-Guide)


## Local Build
See [docs/build.md](docs/build.md) for local build instructions, including local test builds with `--disable-autoupdate`.

## Config

You can configure OpenAsar by clicking the "OpenAsar..." version info in the bottom of your settings sidebar, which will open the config window.
53 changes: 53 additions & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Local Build

The GitHub nightly workflow builds OpenAsar by:

- stamping a `nightly-<commit>` version into `src/index.js`
- stripping the `src/` tree with `node scripts/strip.js`
- packing the final archive with `asar pack`

For local builds, use `scripts/pack.js`, which follows that same flow without modifying your working tree in place.

## Requirements
- `node`
- `asar`

Example install for `asar`:

```bash
npm i -g asar
```

## Build With Normal Auto-Update Behavior
This keeps the default OpenAsar self-update behavior enabled.

```bash
node scripts/pack.js --version nightly-$(git rev-parse --short HEAD) --output tmp/app.asar
```

This build updates from the default upstream release repo:

```text
GooseMod/OpenAsar
```

## Build With A Custom Update Repo
Use this when you want a build to self-update from your own fork releases instead of upstream.

```bash
node scripts/pack.js --update-repo owner/repo --version nightly-$(git rev-parse --short HEAD) --output tmp/app.asar
```

## Build With Auto-Update Disabled
Use this for local testing when you do not want the built `app.asar` to replace itself with the upstream nightly release on launch.

```bash
node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/app.asar
```

## Output
All commands above produce:

```text
tmp/app.asar
```
127 changes: 127 additions & 0 deletions scripts/pack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } = require('fs');
const { join, resolve } = require('path');
const { spawnSync } = require('child_process');

const root = resolve(__dirname, '..');
const tmpRoot = join(root, 'tmp', 'pack-build');

const args = process.argv.slice(2);
let disableAutoUpdate = false;
let updateRepo = 'GooseMod/OpenAsar';
let version;
let output = join(root, 'tmp', 'openasar-build', 'app.asar');

for (let i = 0; i < args.length; i++) {
const arg = args[i];

if (arg === '--disable-autoupdate') {
disableAutoUpdate = true;
continue;
}

if (arg === '--version') {
version = args[++i];
continue;
}

if (arg === '--update-repo') {
updateRepo = args[++i];
continue;
}

if (arg === '--output') {
output = resolve(args[++i]);
continue;
}

if (arg === '--help') {
console.log('Usage: node scripts/pack.js [--disable-autoupdate] [--update-repo <owner/repo>] [--version <value>] [--output <path>]');
process.exit(0);
}

throw new Error(`Unknown argument: ${arg}`);
}

if (!/^[^/\s]+\/[^/\s]+$/.test(updateRepo)) throw new Error(`Invalid --update-repo value: ${updateRepo}`);

if (!version) {
const git = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
cwd: root,
encoding: 'utf8'
});

const shortSha = git.status === 0 ? git.stdout.trim() : 'local';
version = `nightly-${shortSha}`;
}

const stripCode = code => code
.replace(/(^| )\/\/.*$/gm, '')
.replaceAll('const ', 'const~')
.replaceAll('let ', 'let~')
.replaceAll('var ', 'var~')
.replaceAll('class ', 'class~')
.replace(/get [^=}]/g, _ => _.replaceAll(' ', '~'))
.replaceAll('delete ', 'delete~')
.replaceAll(' extends ', '~extends~')
.replaceAll('typeof ', 'typeof~')
.replaceAll(' of ', '~of~')
.replaceAll(' in ', '~in~')
.replaceAll('case ', 'case~')
.replaceAll('await ', 'await~')
.replaceAll('new ', 'new~')
.replaceAll('return ', 'return~')
.replaceAll('function ', 'function~')
.replaceAll('void ', 'void~')
.replaceAll('throw ', 'throw~')
.replaceAll('async ', 'async~')
.replaceAll('else ', 'else~')
.replace('/([0-9]+) files/', '/([0-9]+)~files/')
.replace(/((['"`])[\s\S]*?\2)|[ \n]/g, (_, g1) => g1 || '')
.replaceAll('~', ' ')
.replaceAll('? ?', '??');

const fixHtml = code => code
.replaceAll(' loop', '~loop')
.replaceAll(' autoplay', '~autoplay')
.replaceAll(' src', '~src')
.replaceAll(' id', '~id');

const stripJs = path => writeFileSync(path, stripCode(readFileSync(path, 'utf8')));
const stripHtml = path => writeFileSync(path, stripCode(fixHtml(readFileSync(path, 'utf8'))));
const stripJson = path => {
const data = JSON.parse(readFileSync(path, 'utf8'));
if (data.description) delete data.description;
writeFileSync(path, JSON.stringify(data));
};

const stripTree = dirPath => readdirSync(dirPath, { withFileTypes: true }).forEach(entry => {
const path = join(dirPath, entry.name);

if (entry.isDirectory()) return stripTree(path);
if (entry.name.endsWith('.js')) return stripJs(path);
if (entry.name.endsWith('.json')) return stripJson(path);
if (entry.name.endsWith('.html')) return stripHtml(path);
});

rmSync(tmpRoot, { recursive: true, force: true });
mkdirSync(tmpRoot, { recursive: true });
cpSync(join(root, 'src'), join(tmpRoot, 'src'), { recursive: true });

const indexPath = join(tmpRoot, 'src', 'index.js');
let indexCode = readFileSync(indexPath, 'utf8');
indexCode = indexCode.replace("global.oaVersion = 'nightly';", `global.oaVersion = '${version}';`);
indexCode = indexCode.replace('<disableAutoUpdate>', disableAutoUpdate ? 'true' : 'false');
indexCode = indexCode.replaceAll('<updateRepo>', updateRepo);
writeFileSync(indexPath, indexCode);

stripTree(join(tmpRoot, 'src'));

mkdirSync(resolve(output, '..'), { recursive: true });
const asar = spawnSync('asar', ['pack', join(tmpRoot, 'src'), output], {
cwd: root,
stdio: 'inherit'
});

if (asar.status !== 0) process.exit(asar.status ?? 1);

if (existsSync(output)) console.log(output);
6 changes: 5 additions & 1 deletion src/asarUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper arou
}));

module.exports = async () => { // (Try) update asar
if (global.oaDisableAutoUpdate) return log('AsarUpdate', 'Skipping build-configured auto-update disable');
if (!oaVersion.includes('-')) return;
const releaseChannel = oaVersion.split('-')[0];
const updateRepo = global.oaUpdateRepo || 'GooseMod/OpenAsar';

log('AsarUpdate', 'Updating...');

const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${oaVersion.split('-')[0]}/app.asar`));
const res = (await redirs(`https://github.com/${updateRepo}/releases/download/${releaseChannel}/app.asar`));

let data = [];
res.on('data', d => {
Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ const { join } = require('path');

global.log = (area, ...args) => console.log(`[\x1b[38;2;88;101;242mOpenAsar\x1b[0m > ${area}]`, ...args); // Make log global for easy usage everywhere

const defaultUpdateRepo = 'GooseMod/OpenAsar';
const stampedUpdateRepo = '<updateRepo>';
global.oaVersion = 'nightly';
global.oaDisableAutoUpdate = '<disableAutoUpdate>' === 'true';
global.oaUpdateRepo = stampedUpdateRepo.startsWith('<') ? defaultUpdateRepo : stampedUpdateRepo;

log('Init', 'OpenAsar', oaVersion);

Expand Down Expand Up @@ -37,4 +41,4 @@ if (process.argv.includes('--overlay-host')) { // If overlay
require('discord_overlay2/standalone_host.js'); // Start overlay
} else {
require('./bootstrap')(); // Start bootstrap
}
}
53 changes: 40 additions & 13 deletions src/mainWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,55 @@ const themesync = async () => {

// Settings injection
setInterval(() => {
const versionInfo = document.querySelector('[class*="sidebar"] [class*="compactInfo"]');
if (!versionInfo || document.getElementById('openasar-ver')) return;

const oaVersionInfo = versionInfo.cloneNode(true);
const oaVersion = oaVersionInfo.children[0];
oaVersion.id = 'openasar-ver';
oaVersion.textContent = 'OpenAsar <channel> (<hash>)';
oaVersion.onclick = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o');

oaVersionInfo.textContent = '';
oaVersionInfo.appendChild(oaVersion);
versionInfo.parentElement.parentElement.lastElementChild.insertAdjacentElement('beforebegin', oaVersionInfo);
const openSettings = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o');

const versionInfo =
document.querySelector('.bd-version-info > div:nth-child(2)') ??
document.querySelector('.bd-version-info') ??
document.querySelector('[class*="sidebar"] [class*="compactInfo"]') ??
[...document.querySelectorAll('[class*="sidebar"] [class*="info"] [class*="line"]')].find(x => x.textContent?.startsWith('Host '));

if (versionInfo && !document.getElementById('openasar-ver')) {
const oaVersionInfo = versionInfo.cloneNode(true);
const oaVersion = oaVersionInfo.children?.[0] ?? oaVersionInfo;
oaVersion.id = 'openasar-ver';
oaVersion.textContent = 'OpenAsar <channel> (<hash>)';
oaVersion.onclick = openSettings;

if (oaVersionInfo !== oaVersion) {
oaVersionInfo.textContent = '';
oaVersionInfo.appendChild(oaVersion);
}

const versionTarget = versionInfo.parentElement?.parentElement?.lastElementChild;
if (versionTarget) versionTarget.insertAdjacentElement('beforebegin', oaVersionInfo);
else versionInfo.insertAdjacentElement('afterend', oaVersionInfo);
}

if (document.getElementById('openasar-item')) return;
const sidebar = document.querySelector('[data-list-id="settings-sidebar"]') ?? document.querySelector('[class*="sidebar"] [class*="nav"]');
const appSection = sidebar && (
sidebar.querySelector('ul[aria-label="App Settings"]') ??
[...sidebar.querySelectorAll('ul, [class*="section"]')].find(x => x.getAttribute?.('aria-label') === 'App Settings') ??
[...sidebar.querySelectorAll('ul, [class*="section"]')].find(section => [...section.querySelectorAll('h1, h2, h3, [data-text-variant]')].some(x => x.textContent?.trim() === 'App Settings'))
);
let advanced = document.querySelector('[data-list-item-id="settings-sidebar___advanced_sidebar_item"]');
if (appSection) {
const appItems = [
...appSection.querySelectorAll('[role="listitem"]'),
...appSection.querySelectorAll('[data-list-item-id^="settings-sidebar___"]')
];

advanced = appItems[appItems.length - 1] ?? advanced;
}
if (!advanced) advanced = document.querySelector('[class*="sidebar"] [class*="nav"] > [class*="section"]:nth-child(3) > :last-child');
if (!advanced) advanced = [...document.querySelectorAll('[class*="item"]')].find(x => x.textContent === 'Advanced');
if (!advanced) return;

const oaSetting = advanced.cloneNode(true);
oaSetting.querySelector('[class*="text"]').textContent = 'OpenAsar';
oaSetting.id = 'openasar-item';
oaSetting.onclick = oaVersion.onclick;
oaSetting.onclick = openSettings;

advanced.insertAdjacentElement('afterend', oaSetting);
}, 800);
Expand Down