AW Next is a Windows-only Electron application with a separate background monitor. This page describes the boundaries that matter when changing the project.
Electron main process
├── creates the tray, windows and IPC handlers
├── owns updates, browser-backed lookups and native app integration
├── starts the renderer library UI
└── starts the Watchdog with ELECTRON_RUN_AS_NODE
├── watches processes and achievement files
├── tracks playtime
└── dispatches Windows notifications and overlay events
Electron’s bundled Node runtime runs both processes. The installed app does not ship a second Node or NW.js executable.
| Path | Responsibility |
|---|---|
app/electron/ |
Main process, window lifecycle, IPC, update flow and browser-backed services |
app/app.js |
Renderer entry point and game-library interactions |
app/parser/ |
Platform parsers, discovery, schema lookup and emulator tools |
app/ui/ |
Settings, game view, sorting and renderer-side UI behavior |
app/locale/ |
UI language files and DOM text binding |
app/view/ |
HTML views for the application and overlay |
app/presets/ |
Bundled notification preset assets |
watchdog/ |
Background monitoring, notifications and playtime |
test/ |
App, parser, discovery and locale tests |
watchdog/test/ |
Background-monitor and notification tests |
app/parser/achievements.js normalizes results into the library’s shared game shape.watchdog/notification/transportPolicy.js).Startup must not replay existing unlocks as new events. Watchers should baseline current state before emitting notifications.
The library paints from cache/library_snapshot before discovery starts, so nothing below is on the path
to the first frame. What the scan itself may spend is bounded by three rules:
app/util/dirCache.js memoizes directory listings for the length of one
discovery, because the emulator walk, the nested-appid search and the executable search all cross
the same trees. The memo is only open during discovery; a repair must always see the real folder.app/util/exeCandidateCache.js remembers the
executable candidates of a game folder, keyed by that folder’s own timestamps and stored under
cache/discovery/. Install trees are far larger than anything else a scan reads, and the answer only
changes when the game does.app/util/dirFingerprint.js) and skips the pass
when none moved. That cannot see database or registry sources, so a full pass still runs on a
slower cadence.Per-game metadata is loaded through a small worker pool, never a whole-library Promise.all, and a host
that has proven itself unreachable is not asked again for the rest of the scan
(app/util/networkCircuit.js) - including through the browser fallback, which is the most expensive
path there is.
Parsers normally expose a scan function and an initDebug hook. Their results are normalized by the aggregator rather than rendered directly.
When adding a source:
Current integrations include Steam (legit client and emulator saves), Goldberg/GBE-compatible saves, Goldberg SocialClub, Uplay R2, GOG, Epic, Ubisoft, EA Desktop, Xbox PC, GreenLuma, LumaPlay, SmartSteamEmu, RPCS3, ShadPS4, Xenia, XLiveLessNess (Games for Windows LIVE) and FINAL FANTASY VII (2013). Some platforms have both a legacy mapped-save parser and a newer official/local parser.
| File | Role |
|---|---|
app/parser/achievements.js |
Aggregation, folder discovery and background emulator setup orchestration |
app/parser/installState.js |
Evidence-based installed-state decisions |
app/parser/gameIndex.js |
Persistent game identity and install metadata |
app/parser/steam.js |
Steam schema, metadata and compatible-save parsing |
app/parser/steamAppInfo.js |
Steam’s local appcache/appinfo.vdf: per-appid name and type, read offline and keyless |
app/parser/goldberg.js |
Goldberg/GBE detection, diagnosis, repair and backup |
app/parser/uplayR2.js |
Ubisoft-to-Steam mapping and Uplay R2 schema setup |
app/parser/saveRoots.js |
Known achievement-data and game-library folder names, probed per drive and profile |
app/parser/launcherLibraries.js |
Library roots read from launcher configuration already on disk (Epic manifests, GOG/Ubisoft registry, .GamingRoot) |
app/parser/rpcs3Layout.js |
RPCS3 configuration root and vfs.yml dev_hdd0 resolution |
app/parser/xlln.js |
XLiveLessNess installs: discovery, per-profile unlock records and the schema read from the game’s own executable (xllnSpa.js parses its SPAFILE resource) |
app/parser/ff7.js |
FINAL FANTASY VII (2013): the 8-byte achievement.dat bitfield, paired with Steam api-names |
app/electron/init.js |
Main lifecycle, updater, browser helpers and overlay window |
app/util/updateStatus.js |
Updater state machine shared by the main process, the title bar and Settings |
watchdog/watchdog.js |
Background entry point and service coordination |
watchdog/monitor.js |
Process and filesystem monitoring |
watchdog/notification/toaster.js |
Notification delivery: one plan per event, single owner of the fallback |
watchdog/notification/transportPolicy.js |
Which transport delivers a notification, from observable signals |
watchdog/playtime/monitor.js |
Process-based playtime sessions |
app/util/gameHealth.js |
Per-game health derivation: state, explanation, checks and repairs |
app/util/interfaceMode.js |
Simple / Advanced policy: which tabs, rows and health checks each mode shows |
Packaged user data is stored below %APPDATA%\Achievement Watcher Next. AW Next owns a folder neither predecessor’s uninstaller touches, and imports forward along the chain Achievement Watcher (1.6.8) → Achievement Watcher 3.0 → Achievement Watcher Next on first launch, newest source first. Each hop is one-way and non-destructive: it never deletes a source, never overwrites a file already present in the destination, and stops as soon as the destination holds AW configuration (app/util/migrateUserData.js):
| Directory | Contents |
|---|---|
cfg/ |
Settings, game index, executable mappings and exclusions |
logs/ |
Main, renderer, parser and Watchdog diagnostics |
cache/ and steam_cache/ |
Downloaded tools, metadata and artwork caches |
Media/ |
Legacy seed for custom Windows toast sounds (old registry entries point here; kept for upgrades) |
sounds/, presets/ |
Imported notification sounds and generated user presets |
Settings are stored in cfg/options.ini. Sensitive fields are encrypted before the file is written. Epic account tokens use a separate encrypted cache.
The renderer is a long-lived HTML/JavaScript application. Some locale bindings still depend on DOM order, so changing settings markup can shift text onto the wrong control even when the JSON keys are correct. Update the view, app/locale/loader.js and every locale together, then run the full app suite.
The settings panel has two display modes, Simple and Advanced, stored as [general] interfaceMode
and decided by app/util/interfaceMode.js. The module is pure: it owns the list of Advanced-only
tabs, the data-advanced attribute that marks individual rows inside the tabs Simple keeps, and the
Game Health checks Simple leaves out. app/ui/settings.js applies it by toggling the mode-hidden
class - never by removing markup, because the positional locale bindings above would break.
The mode is a display setting: no parser, watcher or stored value changes with it, and an unset or
unrecognized value resolves to Advanced so an upgrade never hides a feature someone already used.
When adding a settings tab, put its data-view in one of the two lists in interfaceMode.js;
test/ui/interfaceMode.test.js fails on a tab that belongs to neither.
The Sources tab is the exception to the static rules. hiddenOptionalSources() decides per row from
live state: a niche source (OPTIONAL_SOURCES) is folded away only while it is still enabled and
no game in the library carries one of its source values. Switching it off or owning a game it
detected brings the row back, so Simple can never hide the only control that would explain a missing
game. Those rows therefore carry no data-advanced marker - adding one would defeat the rule.
English is the reference locale. app/locale/uiLanguages.js only exposes languages that have a bundled JSON file, and the loader falls back to English at runtime.
app/electron-builder.yml packages the desktop app and copies the Watchdog beside it. Notification presets, sounds and shared Steam-schema mapper modules are unpacked from ASAR because overlay windows and the external Watchdog load them from disk.
npm run build prunes Watchdog development dependencies before packaging. Restore them after a build before returning to development. See BUILD.md and the release workflow.
When changing a parser or watcher, verify:
Next: Build guide - running from source, packaging and the native-module gotchas · Contributing · Release workflow.