Initial commit — Krunker Civilian Client

Cross-platform Krunker.io game client forked from Krunker Police Client
with all KPD/moderator features stripped: no KPD auth, OBS recording,
evidence uploads, yt-dlp, bytenode, or code obfuscation.

Retained: unlimited FPS (custom Electron 42), ad blocking, resource
swapper, matchmaker, userscripts, chat translator, Discord RPC, alt
account manager, configurable keybinds, and advanced Chromium flags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 06:38:15 -08:00
commit 87ddf1499d
34 changed files with 12411 additions and 0 deletions
+292
View File
@@ -0,0 +1,292 @@
# Krunker Civilian Client
Cross-platform Electron-based game client for [Krunker.io](https://krunker.io). Loads the game directly in a BrowserWindow with GPU optimizations, unlimited FPS, persistent sessions, ad blocking, resource swapping, custom matchmaker, userscript engine, and Discord Rich Presence.
## Quick Start
```bash
npm install
npm start # Build all targets + launch Electron
```
For development with sourcemaps:
```bash
npm run dev # Builds in dev mode + launches Electron
```
## Scripts
| Script | Description |
|--------|-------------|
| `npm run dev` | Development build + launch |
| `npm start` | Full build (main + preload) + launch |
| `npm run build` | Build both Vite targets |
| `npm run build:main` | Build main process only |
| `npm run build:preload` | Build preload script only |
| `npm run dist:win` | Build + package for Windows (NSIS installer + portable) |
| `npm run dist:linux` | Build + package for Linux (AppImage + deb) |
| `npm run dist:all` | Build + package for all platforms |
| `npm run clean` | Remove `dist/` and `out/` directories |
## Architecture
### Custom Patched Electron 42
This client uses a custom-patched Electron 42 (Chromium 134, Node 24). The `--disable-frame-rate-limit` Chromium flag — required for unlimited FPS — causes input starvation ("aim freeze") on Chromium 84+. At uncapped frame rates (300+ FPS), the compositor floods the main thread task queue and input events get delayed 50-300ms, then snap to catch up. The `ImplLatencyRecovery`/`MainLatencyRecovery` features mitigated this on Chromium 8793 but were removed in Chromium 94.
#### The Patch
A single-line change in Chromium's main thread scheduler (`main_thread_scheduler_impl.cc`) demotes input tasks from `kHighestPriority` to `kNormalPriority`, allowing the scheduler's built-in anti-starvation logic to fairly interleave input and compositor work:
```diff
case MainThreadTaskQueue::QueueTraits::PrioritisationType::kInput:
- return TaskPriority::kHighestPriority;
+ return TaskPriority::kNormalPriority;
```
Benchmarked via CDP `Input.dispatchMouseEvent`:
| Metric | Before | After |
|--------|--------|-------|
| p99 latency | 97ms | 34ms |
| Max latency | 308ms | 38ms |
| Events >50ms | 8.6% | 0% |
| Frames rendered | baseline | +21% |
| Mouse events processed | baseline | +9% |
The full patch file, GN build args, and step-by-step build instructions are in [`electron-build/`](electron-build/BUILD.md).
#### Binary Distribution
The patched binary is downloaded automatically during `npm install` via `scripts/download-electron.js` from a Gitea release asset (`electron-patched` tag). The `electron` npm dependency (`npm:electron-nightly@42.0.0-nightly.20260227`) provides TypeScript types and the CLI entry point, but the actual binary comes from the patched download.
FPS uncap flags (always applied when enabled):
```
disable-frame-rate-limit + disable-gpu-vsync + max-gum-fps=9999
```
#### Building from Source
Prerequisites: Windows 10/11 x64, ~100 GB disk, 16+ GB RAM, Visual Studio 2022 with C++ workload.
```bash
# 1. Install depot_tools and add to PATH
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 2. Check out Electron source (~40-60 GB)
mkdir C:\electron && cd C:\electron
gclient config --name "src/electron" --unmanaged https://github.com/nicedayzhu/electron.git@v42.0.0-nightly.20260227
gclient sync --with_branch_heads --with_tags
# 3. Apply the patch
cd src
git apply path/to/electron-build/input-priority-fix.patch
# 4. Configure and build (~2-4 hours for release)
gn gen out/Release
cp path/to/electron-build/args.release.gn out/Release/args.gn
gn gen out/Release
ninja -C out/Release electron
# 5. Create distributable zip
ninja -C out/Release electron:dist_zip
```
See [`electron-build/BUILD.md`](electron-build/BUILD.md) for detailed instructions, testing builds, and verification steps.
### How the Game Loads
The game is loaded **directly** in the main `BrowserWindow` via `win.loadURL('https://krunker.io')`. This gives the game full GPU compositing, identical to Chrome.
### Process Model
```
Main Process (src/main/index.ts)
|
|-- Creates BrowserWindow loading krunker.io directly
|-- Manages session (persist:krunker), UA cleaning, permissions
|-- Ad blocking + resource swapping via webRequest.onBeforeRequest
|-- Configurable keybinds via before-input-event
|-- Matchmaker IPC trigger (sends config to preload on hotkey)
|-- CSS injection (ad hiding, client settings, matchmaker popup, keybind dialog)
|-- File-based logging (electron.log in userData/logs/)
|-- IPC handlers for window controls, config, devtools, userscripts
|-- Userscript manager (filesystem scanning, tracker, preferences)
|-- Auto-updater (Gitea releases API check, Setup.exe download + launch)
|-- Discord Rich Presence via raw IPC socket
|-- Consent dismiss via polling (no MutationObserver on main frame)
|
+-- Preload Script (src/preload/index.ts)
|-- IPC bridge (window.kpc)
|-- Action button grid (folder shortcuts, log viewers, reset/restart)
|-- Settings UI hooks (collapsible sections: swapper, matchmaker, chat, translator, accounts, advanced, userscripts)
|-- Userscript engine (metadata parser, execution, CSS injection, settings)
|-- Matchmaker IPC listener -> fetchGame() in matchmaker.ts
|-- Chat translator (Google Translate API, MutationObserver on #chatList)
|-- Exit button (exposes Krunker's native #clientExit element)
|-- Keybind capture dialog (Crankshaft-style)
```
### Source Files
```
src/
main/
index.ts Main process — window, IPC, session, ad blocking, keybinds
platform.ts OS detection, Chromium GPU/performance flags (per-platform)
config.ts electron-store schema, defaults, DEFAULT_KEYBINDS
client-ui.ts Injected CSS for settings panel, keybind dialog, matchmaker popup
swapper.ts Resource swapper — local asset overrides via custom protocol
userscripts.ts Userscript manager — filesystem scanning, tracker.json, preferences
discord-rpc.ts Discord Rich Presence via raw IPC socket
logger.ts File logging with daily rotation and 7-day retention
updater.ts Auto-updater — Gitea releases API check, download, installer launch
update-window.ts Update progress BrowserWindow with inline HTML + progress bar
preload/
index.ts IPC bridge, settings UI hooks, keybind capture, matchmaker listener, alt manager
utils.ts Shared types (SavedConsole, KeybindDef), helpers (escapeHtml, genChatMsg)
matchmaker.ts Custom matchmaker — lobby fetch, filtering, popup UI
translator.ts Real-time chat translation via Google Translate API
userscripts.ts Userscript engine — metadata parser, execution, CSS injection, state
```
### Build System
Two Vite configs build independent targets:
| Config | Target | Output | Notes |
|--------|--------|--------|-------|
| `vite.main.config.ts` | Main process | `dist/main/index.js` (CJS) | Externalizes `electron`, `electron-store`, and Node builtins. Targets Node 20. |
| `vite.preload.config.ts` | Preload script | `dist/preload/index.js` (CJS) | Externalizes `electron` and `uuid`. Targets Node 20. |
### Custom Electron Binary
The patched Electron binary is managed by `scripts/download-electron.js`:
- Downloads from Gitea release tag `electron-patched`, asset `electron-v42.0.0-nightly-patched-win32-x64.zip`
- Replaces `node_modules/electron/dist/` with the patched binary
- Runs automatically as a `postinstall` script during `npm install`
- Skips download if the version file already matches
### Configuration (electron-store)
Settings are persisted in `krunker-civilian-config.json` (OS-specific app data directory). Schema defined in `src/main/config.ts`:
```typescript
interface AppConfig {
window: { width, height, x, y, maximized, fullscreen }
performance: { fpsUnlocked, hardwareAccel, gpuPreference }
game: { lastServer, socialTabBehaviour, joinAsSpectator }
swapper: { enabled, path }
userscripts: { enabled, path }
matchmaker: { enabled, regions, gamemodes, minPlayers, maxPlayers, minRemainingTime, openServerBrowser }
keybinds: { reload, newMatch, copyGameLink, joinFromClipboard, devTools, matchmaker,
matchmakerAccept, matchmakerCancel, pauseChat, fullscreenToggle }
ui: { showExitButton, deathscreenAnimation, hideMenuPopups }
discord: { enabled }
translator: { enabled, targetLanguage, showLanguageTag }
advanced: { removeUselessFeatures, gpuRasterizing, helpfulFlags, disableAccelerated2D, increaseLimits,
lowLatency, experimentalFlags, angleBackend }
accounts: SavedAccount[]
platform: { detectedOS, gpuBackend }
}
```
### Platform Handling
| Feature | Windows | Linux |
|---------|---------|-------|
| Window chrome | Standard OS frame (`frame: true`) | Standard OS frame (`frame: true`) |
| GPU backend | D3D11 via ANGLE (default, configurable) | Default (+ `ozone-platform-hint auto` for Wayland) |
| GPU sandbox | Default | Disabled (AppImage FUSE + Mesa driver compat) |
Common flags always applied: `disable-backgrounding-occluded-windows`, `ignore-gpu-blocklist`. FPS uncap flags (`disable-frame-rate-limit`, `disable-gpu-vsync`, `max-gum-fps=9999`) applied when enabled. The ANGLE backend is configurable (default D3D11 on Windows; options include OpenGL, Vulkan, D3D9, D3D11on12). Additional Chromium flags (GPU rasterization, useful features, low latency, etc.) are configurable via the Advanced settings panel.
### Session & User-Agent
- Uses `partition: 'persist:krunker'` for persistent login/settings across restarts
- User-Agent is cleaned to strip the app name identifier (keeps `Electron/` so Krunker enables its Client settings tab)
## Features
### Core Client
- Unlimited FPS via custom-patched Electron 42 build
- Ad blocking (network-level URL filter + CSS hiding)
- Resource swapper (replace game textures, sounds, models with local files)
- Custom matchmaker (filter lobbies by region, gamemode, player count, remaining time)
- Userscript system (Tampermonkey-style metadata, custom per-script settings, instant toggle via unload)
- Chat translator (real-time translation via Google Translate API with language tags)
- Exit button in Krunker's sidebar menu (exposes native `#clientExit` element)
- Selectable + pausable chat (always-on text selection, F10 to freeze auto-scroll for reading history)
- Configurable keybinds with Crankshaft-style rebinding dialog
- Configurable ANGLE backend (D3D11, OpenGL, Vulkan, D3D9, D3D11on12 — platform-filtered)
- Advanced Chromium flag settings (GPU rasterization, low latency, experimental features, and more)
- Client settings tab in Krunker's settings panel with collapsible sections
- Action button grid in settings (folder shortcuts, log viewers, reset/restart options)
- File-based logging (electron.log in userData/logs/, daily rotation with 7-day retention)
- Persistent game session, window state persistence, cookie consent auto-dismiss
- Auto-updater (Gitea releases API check, Setup.exe download with progress window, installer launch)
- Discord Rich Presence (game state, map, mode, player count)
- Death screen animation blocker (prevents CSS animation performance impact at uncapped FPS)
- Menu popup hider (hides store ads, stream containers, news popups)
### Alt Manager (Account Switching)
- **Account storage** — Save multiple Krunker accounts (label, username, password)
- **Quick switch** — In-game "Accounts" menu item opens Krunker's native popup with saved accounts
- **Login automation** — Auto-logout + `loginOrRegister()` credential fill
- **Settings section** — Full account CRUD in the collapsed "Accounts" settings section
- **Credential obfuscation** — Credentials stored with character-shift encoding to prevent casual config reading
## Known Constraints
### MutationObserver Breaks WebGL on Main Frame
A `MutationObserver` with `{ childList: true, subtree: true }` on Krunker's main frame DOM causes the WebGL 3D engine to hang indefinitely. Consent dismiss uses polling (`setInterval`) on the main frame instead. MutationObserver is safe in iframes and targeted elements like `#menuWindow`.
### MutationObserver Infinite Loops (Chromium 94+)
Observers that modify elements within their observed subtree can cause infinite loops. In Chromium 94+, even `textContent` assignments that don't change the value still create new text nodes (triggering `childList` mutations). Observers must disconnect before DOM modifications and reconnect after.
### Synthetic Events Lack User Activation (Chromium 94+)
Events created via `new MouseEvent()` / `new KeyboardEvent()` have `isTrusted: false` and do not provide user activation. Krunker ignores untrusted input events. The client uses `webContents.sendInputEvent()` via IPC to inject trusted input events from the main process.
### Custom Electron Build Required
The `--disable-frame-rate-limit` flag causes compositor input starvation on all Chromium versions 84+. The `ImplLatencyRecovery`/`MainLatencyRecovery` features that mitigated this were removed in Chromium 94. Our custom Electron 42 build patches the Chromium main thread scheduler to fairly interleave input and compositor work. See [Building from Source](#building-from-source) above and [`electron-build/BUILD.md`](electron-build/BUILD.md) for full instructions.
### Uncapped FPS and CSS Animations
At uncapped frame rates (600+ FPS), Krunker's CSS animations (e.g. death screen slide-in) force a layout recalculation on every frame, which can saturate the renderer main thread. The death screen animation blocker (`ui.deathscreenAnimation`) is enabled by default to prevent this.
## Tech Stack
- **Electron** 42 (Chromium 134, Node 24) — Custom-patched build for unlimited FPS
- **TypeScript** 5.7 — Type-safe source code
- **Vite** 6 — Fast bundler (2 build targets: main + preload)
- **electron-store** 8 — JSON config persistence (CJS)
- **uuid** 9 — Unique ID generation
- **electron-builder** 26 — Cross-platform packaging (NSIS, portable, AppImage, deb)
## Project Structure
```
Krunker-Civilian-Client/
src/
main/ Main process source (10 modules)
preload/ Preload script source (5 modules)
dist/ Vite build output
main/
preload/
out/ electron-builder packaged output
build/ Build resources (icons, .desktop file)
scripts/ Build scripts (Electron patched binary download)
electron-build/ Custom Electron build instructions and patches
vite.main.config.ts
vite.preload.config.ts
electron-builder.yml
tsconfig.json
package.json
```