11# Hacker Launcher
22
3- ** v1.0** — A game launcher for running Windows games on Linux with Proton, Wine, Steam, Flatpak, and Native runners.
3+ ** v1.0** — A game launcher for running Windows games on Linux with Proton, Wine, Steam, Flatpak, and Native runners — plus Android apps via Waydroid .
44
55Built with ** Rust + Tauri 2** on the backend and ** SolidJS + TypeScript** on the frontend.
66
@@ -9,25 +9,35 @@ Built with **Rust + Tauri 2** on the backend and **SolidJS + TypeScript** on the
99## Features
1010
1111### Games
12- - Add games with Native / Wine / Proton / Flatpak / Steam runners
12+ - Add games with Native / Wine / Proton / Flatpak / Steam / Android runners
1313- Per-game DXVK, Esync, Fsync, DXVK-Async overrides (with global defaults in Settings)
1414- Custom per-game environment variables
1515- Custom icon/cover art per game, shown in both List and Grid library views
1616- Tags and favorites, with filtering in the library
1717- List view or cover-art Grid view (switchable in Settings, takes effect after restart)
18- - Drag & drop a ` .exe ` anywhere onto the window to add it as a game
18+ - Drag & drop a ` .exe ` or ` .apk ` anywhere onto the window to add it as a game
1919- Live "Running" status with a Stop button, and automatic playtime tracking
2020- Rotating per-run logs (last 10 runs kept per game) with a log viewer
2121- Keyboard shortcuts: ** Enter** launches/stops the selected game, ** Delete** removes it
2222- Quote-aware launch options parsing (so ` --config="C:\Path With Spaces\x.ini" ` works correctly)
2323- Optional shared Wine prefix across multiple games, in addition to the default per-game prefix
2424- Steam library auto-import: scans default, Flatpak, and additional Steam Library Folder
2525 locations — including external/removable drives and unregistered ` SteamLibrary ` folders — and
26- lets you review and correct the guessed executable before importing
27- - ProtonDB compatibility lookup by Steam App ID (cached for 15 minutes per App ID)
26+ lets you review and correct the guessed executable before importing. The exe guess uses a
27+ multi-signal scoring heuristic (name similarity to the game's title, Unreal/Unity "-Shipping"
28+ naming, install-folder depth, known non-game subfolders), not just "pick the largest file"
29+ - ProtonDB compatibility lookup by Steam App ID (in-memory cache for 15 minutes, plus a
30+ disk-persisted cache so a lookup that already succeeded once still works offline afterwards).
31+ A built-in Steam store search lets you find an App ID by name for games with none on record
2832- Backup/restore: export games + settings to a JSON file, and import it back later either as a
2933 full restore or merged in as new games only
3034
35+ ### Android
36+ - Add an ` .apk ` as a game (runner "Android"), running under [ Waydroid] ( https://docs.waydro.id )
37+ - Package name auto-detected from the APK via ` aapt ` /` aapt2 ` (editable if detection fails)
38+ - One-click install into the Waydroid container with live streaming progress
39+ - Launch/stop by package ID, independent of the ` .exe ` -based runners' Wine/Proton pipeline
40+
3141### Proton / Wine tooling
3242- Install Proton-GE, official Valve Proton (stable/experimental), or a custom ` tar.gz ` /folder
3343- Paginated GitHub release listing (not just the first ~ 30), with an in-memory cache to avoid
@@ -41,23 +51,29 @@ Built with **Rust + Tauri 2** on the backend and **SolidJS + TypeScript** on the
4151 your installed ` winetricks list-all ` )
4252- ** Dependency scanner** : looks for bundled redistributable installers (` vcredist ` , ` dotnetfx ` ,
4353 ` dxsetup ` , …) next to a game's executable and offers to run them directly or via the matching
44- Winetricks verb
54+ Winetricks verb. Also flags dependencies that already look installed in the target prefix (via
55+ known marker DLLs/paths), so you're not nudged to reinstall something that's already there
4556- Live streaming output for both Winetricks and dependency-installer runs — long operations like
4657 ` dotnet48 ` show real progress instead of an unresponsive-looking wait
4758- ** Prefix locking** : launching a game, running Winetricks, and running a dependency installer all
4859 claim an exclusive lock on the Wine prefix they touch. A second attempt against the same prefix
4960 (e.g. starting a game that shares a prefix with one already running, or running Winetricks while
50- that happens) is refused with a clear error instead of risking registry corruption
61+ that happens) is refused with a clear error instead of risking registry corruption — this now
62+ also detects prefixes already in use by processes * outside* the launcher (e.g. a ` wine ` command
63+ run manually in a terminal, or a second launcher instance), not just other launcher-initiated ops
5164- Gamescope integration (adaptive-sync, resolution, FPS cap, Big Picture) via the ` --gamescope `
5265 launch option
5366
5467### Controllers
5568- Lists gamepads currently visible to the kernel (` /proc/bus/input/devices ` )
5669- ** SDL_GAMECONTROLLERCONFIG mapping wizard** : pick a connected controller, press each button/stick
5770 in turn, and the wizard builds the mapping string for you — reading raw Linux joystick events
58- directly, so you don't need to know button/axis numbers by heart. The controller's SDL-style GUID
59- is auto-derived from its USB/Bluetooth identity in sysfs. Usable standalone (copies to clipboard)
60- or launched straight from a game's Configure dialog (fills the field directly)
71+ directly, so you don't need to know button/axis numbers by heart. D-Pads reported by the kernel
72+ as a "hat" (rather than four separate buttons) are detected via the same ` JSIOCGAXMAP ` ioctl
73+ SDL2/` jstest ` use, and encoded as SDL's ` hH.MASK ` syntax rather than a plain axis. The
74+ controller's SDL-style GUID is auto-derived from its USB/Bluetooth identity in sysfs. Usable
75+ standalone (copies to clipboard) or launched straight from a game's Configure dialog (fills the
76+ field directly)
6177- Per-game "Disable Steam Input" toggle and raw ` SDL_GAMECONTROLLERCONFIG ` override
6278
6379### UI
@@ -74,6 +90,9 @@ Built with **Rust + Tauri 2** on the backend and **SolidJS + TypeScript** on the
7490 catalog browser shell out to your installed copy)
7591- ` gamescope ` — optional, for Gamescope integration
7692- ` steam ` or ` flatpak ` — for the Steam runner and Steam library import
93+ - ` waydroid ` — for the Android runner (see [ docs.waydro.id] ( https://docs.waydro.id ) for setup);
94+ ` aapt ` or ` aapt2 ` (from your distro's Android SDK build-tools package) — optional, used to
95+ auto-detect an APK's package name; without it you can still enter the package ID manually
7796- Proton versions are downloaded and managed by the launcher itself
7897
7998### Build dependencies
@@ -108,13 +127,10 @@ npm run tauri dev
108127
109128# Production build
110129npm run tauri build
111- # Binary will be in: src-tauri/target/release/hacker-launcher
130+ # Packaged AppImage/.deb/.rpm will be in: src-tauri/target/release/bundle/
131+ # (plain binary also at: src-tauri/target/release/hacker-launcher)
112132```
113133
114- > ** Note:** ` bundle.active ` is currently ` false ` in ` tauri.conf.json ` , so ` tauri build ` produces a
115- > plain binary rather than a packaged AppImage/` .deb ` /` .rpm ` . Flip it on and configure the ` bundle `
116- > section if you want installable packages.
117-
118134## Architecture
119135
120136```
@@ -135,9 +151,17 @@ source-code/
135151 ├── config_manager.rs Settings persistence
136152 ├── game_manager.rs Game CRUD, process launch/tracking/playtime, log rotation
137153 ├── proton_manager.rs GitHub release listing/cache, install/extract, checksums
138- ├── tools.rs Steam import, Winetricks, dependency scan, ProtonDB, controllers,
139- │ backup/restore
140- └── prefix_lock.rs Cross-cutting Wine-prefix exclusivity lock
154+ ├── android_manager.rs Waydroid: APK package-name extraction, install, launch, running-check, stop
155+ ├── prefix_lock.rs Cross-cutting Wine-prefix exclusivity lock
156+ └── tools/ Loosely-related features, one submodule per area:
157+ ├── mod.rs Re-exports each submodule's public API as `tools::*`
158+ ├── process_util.rs Shared "stream a child process's output live" helper
159+ ├── steam_import.rs Library scan, appinfo.vdf launch-config read, exe-scoring fallback
160+ ├── winetricks.rs Verb catalog + running winetricks against a prefix
161+ ├── dependency_scan.rs Bundled-installer detection + already-installed heuristic
162+ ├── controllers.rs Controller listing + SDL mapping wizard input capture
163+ ├── protondb.rs ProtonDB lookup with in-memory + disk cache
164+ └── backup.rs Export/import of games + settings
141165```
142166
143167## Data locations
@@ -156,17 +180,50 @@ All data is stored in `~/.hackeros/Hacker-Launcher/`:
156180
157181Being upfront about what these features actually are, rather than overselling them:
158182
159- - ** Steam library import** and the ** dependency scanner** are filename/heuristic-based (largest
160- non-utility ` .exe ` in a folder; known installer filename patterns). They're right most of the
161- time, wrong occasionally — the import dialog lets you fix the guessed executable before
162- committing, and the dependency scanner can't tell whether something is * already* installed in the
163- prefix, only that an installer for it exists on disk.
164- - ** ProtonDB lookup** and ** Steam library import** both need network/Steam-client data respectively;
165- neither works offline or for games with no corresponding Steam App ID.
183+ - ** Steam library import** 's exe guess now tries Steam's own local launch-config cache first
184+ (` appcache/appinfo.vdf ` — an undocumented, community-reverse-engineered binary format, parsed
185+ defensively with validation on the result), so it's reading Steam's real answer rather than
186+ guessing whenever that succeeds. When it doesn't (no local cache entry, or it doesn't parse — e.g.
187+ a future Steam client revision changes the format), it falls back to the filename/scoring
188+ heuristic as before. The import dialog tags each game with which one actually happened ("steam
189+ metadata" vs "heuristic scan") and always lets you correct the path either way before committing.
190+ ** ProtonDB lookup** and ** Steam library import** both need network/Steam-client data respectively
191+ to do a * fresh* lookup — ProtonDB results are now cached to disk so a prior successful lookup is
192+ still available offline, but a first-time lookup with no connection still fails, and neither works
193+ for games with no corresponding Steam App ID (the "Find by name" search only helps if the game
194+ * has* a Steam store entry, just not one on record locally).
195+ - The ** dependency scanner** 's "already installed" check combines two signals, both still
196+ heuristics rather than a real registry/component query (Wine doesn't expose one): winetricks' own
197+ install log (` <prefix>/winetricks.log ` , checked first — a real record of what winetricks
198+ successfully ran against that exact prefix, not an inference) and, as a fallback, known marker
199+ DLLs/paths a given component would leave behind. The log-based check only knows about verbs
200+ installed * via* winetricks; the marker-file check only covers a fixed table of common verbs —
201+ anything installed some other way and not in that table still always shows as "not confirmed
202+ installed".
166203- The ** controller mapping wizard** reads raw Linux joystick events directly rather than depending
167- on SDL2 itself — it's a best-effort helper, and D-Pads reported as a "hat" (rather than four
168- separate buttons) may need a manual touch-up to the generated string.
169- - ** Prefix locking** prevents concurrent * launcher-initiated* operations on the same prefix; it
170- can't stop something outside the launcher from touching the same prefix at the same time.
171- - No automated tests or CI yet, and the launcher itself has no auto-update mechanism (only Proton
172- versions are update-checked).
204+ on SDL2 itself. D-Pads reported as a "hat" are detected and mapped correctly via the same
205+ ` JSIOCGAXMAP ` ioctl SDL2 uses, but this ioctl can fail silently on unusual devices/permissions, in
206+ which case the wizard falls back to treating every axis as plain analog — it's still a best-effort
207+ helper overall, so double-check the generated string.
208+ - ** Prefix locking** detects Wine prefixes already in use by processes outside the launcher via two
209+ combined ` /proc ` checks: a matching ` WINEPREFIX ` environment variable, and (to also catch
210+ processes that reach a prefix without ever setting that variable themselves) any open file
211+ descriptor pointing inside the prefix directory. Both are still limited to processes readable by
212+ the current user (same user, or root) — a process running as a different user touching the same
213+ prefix on a shared machine is invisible to either check — and both are inherently racy against a
214+ process that hasn't opened anything inside the prefix yet at the moment of the check.
215+ - ** Android/.apk support** runs entirely inside a separate [ Waydroid] ( https://docs.waydro.id )
216+ container rather than as a process this launcher directly owns, so it doesn't get quite the same
217+ guarantees as the Wine/Proton runners. Playtime tracking now polls the container (`waydroid shell
218+ -- pidof <package >`, roughly every 4 seconds with a 2-strike debounce) to detect the app's actual
219+ exit rather than only reacting to the Stop button, so a game closed from inside Android itself
220+ still gets recorded correctly — but it's still polling-based (exit is noticed with a delay of up
221+ to ~ 8 seconds, not instantly) and, like ` pidof ` generally, assumes the app's main process is named
222+ after its package ID, which can miss/misreport for apps that spawn background services under a
223+ different name. Stop also calls ` waydroid app stop ` , which isn't supported on every Waydroid
224+ version. Package-name auto-detection needs ` aapt ` /` aapt2 ` installed separately (Waydroid itself
225+ doesn't provide it) — without it, the package ID can still be entered by hand.
226+ - No automated tests or CI pipeline yet — some of the trickier logic above (the binary VDF parsing,
227+ the winetricks-log check) does have unit tests alongside it in the relevant module now, but
228+ there's no CI running them automatically and no coverage of the Tauri command layer or frontend.
229+ The launcher itself also has no auto-update mechanism (only Proton versions are update-checked).
0 commit comments