Repository navigation
Releases: PoizenJam/RockSniffer
Release list
v0.6.11
RockSniffer_PJ v0.6.11 Release Notes
Hotfix release on top of v0.6.10. No C# changes; addon JavaScript and shared _deps/playthrough-tracker.js only.
Bug fixed
Mode-1 (results) "X% worse than previous best" reports a bogus value
In v0.6.10, the addons' onSongEnded captures app.snapshotFinal = tracker.getFinal() into Vue data so the comparison view survives the tracker's reset on next-song's onSongChanged. But addon.onSongEnded runs before tracker.onSongEnded in _doOnSongEnded's callback order, so at snapshot time the tracker's currentAttempt.sections[last] is still the constructor's {} placeholder — update() only finalizes a section when the user crosses into the next one, and the song's final section never has a next.
Sequence:
tracker.getFinal()readssections[last].Accuracy→undefined(placeholder) → ternary defaults to0RelativeAccuracy = 0 - previousBest.sections[last].Accuracy=-previousBest.lastSection.Accuracy- Mode-1 HTML displays
Math.abs(RelativeAccuracy)% worse than previous best
The message reports the user's previous best accuracy as a negative delta. When a user re-plays a song at their previous skill level, the bug produces output like "80.4% worse than previous best" alongside a live "Notes: 80.4%" stat — the two numbers visually match because the user tied their record, not because the system is reporting current accuracy.
Fix
Two changes:
addons/_deps/playthrough-tracker.js — PlaythroughBySection.finalize() is now idempotent. After first call, currentSection is undefined; subsequent calls return immediately. This makes it safe for addons to call finalize() early without disrupting the tracker's own later call.
Each mode-1 addon's onSongEnded — calls tracker.currentAttempt.finalize(poller.getCurrentReadout()) immediately before the snapshot capture. This ensures sections[last].Accuracy is correctly populated from the SONG_ENDING readout's noteData before tracker.getFinal() reads it.
poller.getCurrentReadout() returns _prevdata.memoryReadout — the previous poll's data, which is the SONG_ENDING-state readout containing the final accumulated noteData. This is the same source the tracker itself uses for its own finalize call.
Applied to 9 addons total:
- 5 in the main repo:
current_song_v2,current_song_v3,current_song_v3.1_LaS,Arcade_v1_LaS,Arcade_v1_SA - 4 in the PJ addons set:
PoizenJam MINIMALIST,PoizenJam MINIMALIST V2,PoizenJam RETROWAVE,PoizenJam SYNTHWAVE
Files changed
addons/_deps/playthrough-tracker.js— idempotentfinalize()guardaddons/current_song_v2/script.js— earlyfinalize()call inonSongEndedaddons/current_song_v3/script.js— sameaddons/current_song_v3.1_LaS/script.js— same (byte-identical to v3)addons/Arcade_v1_LaS/script.js— sameaddons/Arcade_v1_SA/script.js— sameRockSniffer/Program.cs— version bump 0.6.10 → 0.6.11
Separate PJ addons set updated correspondingly.
No submodule changes. JS-only (the version-constant change is the only C# touch).
v0.6.10
RockSniffer_PJ v0.6.10 Release Notes
Hotfix release on top of v0.6.9. No C# changes; addon JavaScript only.
Bug fixed
Mode-1 (results comparison) wiped before user can read it in NSP
The 6 mode-1 addons (current_song_v2/v3/v3.1_LaS/v4, Arcade_v1_LaS/SA) display a post-song results view in mode 1, showing the prevNotes stats, the hasPreviousBest() comparison ("X% better than previous best"), and the cycled feedback strings ("Hit N more notes in section X", etc.). This is set by onSongEnded.
In Nonstop Play, when a song completes Rocksmith returns to nonstopplayhub (state IN_MENUS) and the in-memory songID immediately flips to the next-queued song's ID. This triggered onSongChanged in the same poll as onSongEnded, and the v3/v3.1_LaS/Arcade addons' existing onSongChanged handler flipped mode back to 0 before the comparison view was ever rendered to the user.
v2 and v4 didn't have an onSongChanged handler, so they worked better by accident — but still inconsistently, depending on getCurrentArrangement() timing relative to the songID flip.
Fix: per-addon minimum-hold for mode 1
Each of the 6 mode-1 addons now holds mode 1 for at least feedback.length × CYCLE_MS (where CYCLE_MS = 5000, matching the existing cycleFeedback rotation interval) before any subsequent onSongChanged/onSongStarted callback can flip back to mode 0. Songs with rich feedback (e.g. 5 strings → 25s hold) display longer; "YOU TRIED!"-only fallback songs hold for one cycle (5s).
State transitions to SONG_SELECTED, SONG_STARTING, or SONG_PLAYING override the min-hold immediately — once the user has clearly progressed past the hub/result screen into the next song, the comparison view is no longer useful and flips out without waiting for the timer.
Implementation: new flipToModeZero(force) helper plus modeOneSetAt / pendingModeFlipTimer module-level state in each addon. The existing 60-second hideTimeout in generateFeedback remains as the ultimate upper bound (mode 1 hides + addon goes invisible after 60s if nothing else fires).
v2 and v4 also gain an onSongChanged handler for behavioral consistency with v3/v3.1/Arcade.
Adjacent fix: song-name marquee matches stats in mode 1
The song computed property in all 6 addons now reads from prevData.songDetails when mode === 1, so the song name marquee, album art, and artist all reference the song that just ended — consistent with the prevNotes stats and hasPreviousBest comparison shown below.
Without this, the new extended hold window in NSP would have exposed a cosmetic mismatch: the marquee would show song B (the next-queued song) while the stats panel showed song A's data. In LaS this is a no-op (songID doesn't change until the user picks the next song, so songDetails and prevData.songDetails are already the same).
Files changed
addons/current_song_v2/script.js— min-hold helper + onSongChanged added + onStateChanged override + song computed prevData readaddons/current_song_v3/script.js— min-hold helper + lifecycle handlers + song computedaddons/current_song_v3.1_LaS/script.js— identical to v3 (byte-for-byte)addons/current_song_v4/script.js— min-hold helper + onSongChanged added + lifecycle handlers + song computedaddons/Arcade_v1_LaS/script.js— min-hold helper + lifecycle handlers + song computedaddons/Arcade_v1_SA/script.js— sameRockSniffer/Program.cs— version bump 0.6.9 → 0.6.10
No submodule changes. No C# rebuild needed for the addon work itself; only Program.cs's version constant changed.
v0.6.9
RockSniffer_PJ v0.6.9 Release Notes
Major changes
Completion-flag determination rewrite
The completed flag for force-end paths (SONG_PLAYING→timer=0, songID-change force-end, gameStage force-end) is now decided by deterministic signals rather than the pre-v0.6.9 maxTime ≥ songLength - 0.5s heuristic.
A new helper DetermineCompletedForForceEnd() returns false if any of three documented cases applies:
-
Pause-driven exit/restart/skip —
currentState == SnifferState.SONG_PAUSEDat the moment of force-end (UpdateState() hasn't run yet this poll). Catches pause→restart that unloads the chart, pause→skip in Nonstop Play, and any pause-driven flow that fires a force-end before the state machine transitions. -
No-input boot at song start —
maxTime < 0.1s. Rocksmith's audio-input-loss abort only fires before the timer advances meaningfully; once playback is moving, an audio-input drop triggers auto-pause instead. The 0.1s floor is well below any legitimate play time and well above the float-near-zero initialization flashes (e.g. 9.2515e-37) that can appear briefly during chart load. -
Score Attack 3-strike fail —
FailedPhrases >= 3in the SA note data. Checked FIRST so a last-phrase fail (wheremaxTimewould otherwise look like a natural completion) still resolves tocompleted=false.
Otherwise: presumed natural completion → completed=true. This fixes two prior false-negative sources:
- Natural completion that missed the SONG_ENDING state transition due to the 0.201s poll-cadence race window
- Nonstop Play song transitions where the previous song's
maxTimeended fractionally belowsongLength - 0.5s
The paused field semantics (sticky across pause→resume) are unchanged — it continues to record "was the user paused at any point during this run" for the SQL/CSV/event log columns.
Vocals addon — karaoke-style line slide
Substantial rewrite of addons/vocals/script.js:
- DOM-state-tracked per-line elements (replaces rebuild-HTML-string approach) so CSS transitions can fire across polls
- Lines slide upward through four slot positions:
incoming(off-screen below) →preview(bottom row) →active(top row) →outgoing(off-screen above) - Active line semantics changed to "most recently STARTED line" so just-sung lines stay visible during gaps between lines (matches Rocksmith's in-game behavior)
- Preview line suppressed during pre-song anticipation window, appears once the active line has actually started being sung
New tunable settings at top of script.js:
latencyCompensation: 0.3(was 0.250 in pre-v0.6.9, briefly 0.5 in v0.6.9 development)lineAnimationDuration: 0.5(animation duration in seconds; 0 disables animation)lineHeight: '1.2em'(per-line height, sizes the .vocal container accordingly)
postVocalDisplayTime is now unused (line advancement is driven by lineAnimationDuration instead) but retained for backward-compat. The <span class="lyric"> wrapper is preserved around per-line syllable spans for back-compat with twitch/kick/legacy themes that style it.
sniffer-poller.js STATE_* string constants
The STATE_* constants at the top of addons/_deps/sniffer-poller.js were changed from integers (STATE_SONG_PLAYING = 4, etc.) to strings (STATE_SONG_PLAYING = "SONG_PLAYING", etc.) to match the v0.6.7 JSON output, which uses [JsonConverter(typeof(StringEnumConverter))] on the currentState field of AddonServiceListener.JsonResponse.
Fixes silent breakage of:
- Vocals addon (gate at script.js:27 was always-false because string ≠ integer)
- Phrase color coding in current_song_v2/v3/v3.1_LaS/v4 and Arcade_v1_LaS/SA (gate in playthrough-tracker.js:115 was always-false, so per-phrase accuracy was never populated, and
getPhraseAccuracyreturned 'Rest' →accuracyGradient('Rest')returned grey) - accuracy_chart silently broken (same gate at script.js:11)
- Internal lifecycle transitions in sniffer-poller.js itself (lines 133, 140, 173)
One file edit (_deps/sniffer-poller.js) cascades the fix to all consumers — no per-addon edits needed.
New output-format tokens
Added three new tokens for output.json configuration:
%CURRENT_PATH%— human-readable arrangement type ("Lead", "Rhythm", "Bass"). Populated from launch (defaults to "Lead"), updates when the user switches Path at the menu level. Useful for path-specific OBS scene switching.%CURRENT_PATH_BYTE%— raw byte value (1/2/4) backing the path. Niche but available.%TUNING%— tuning name of the currently-loaded arrangement (e.g. "E Standard", "D Standard (Capo Fret 2)"). Resolved via arrangementID match againstdetails.arrangements[].
A new default output entry path.txt is added to the default output.json (writes %CURRENT_PATH%). Empty string is written when the relevant memory field is unavailable; downstream OBS text sources hide automatically when the file is empty.
Known gaps preserved from v0.6.8
- Multiplayer mode disambiguation: most Multiplayer sub-modes share gameStages with single-player counterparts (
las_gameetc.), so theDeriveModeFromGameStageclassifier alone cannot distinguish them. The MULTIPLAYER mode gate at PlaythroughHistory and the JS tracker stays in place; full per-player tracking with CE-discovered MP-active flag and P2 note-data structs is v0.7.0 scope.
Files changed
RockSnifferLib/Sniffing/Sniffer.cs— addedDetermineCompletedForForceEnd()helper, updated three force-end call sitesRockSniffer/Program.cs— added%CURRENT_PATH%,%CURRENT_PATH_BYTE%,%TUNING%token substitutions; version bump 0.6.8 → 0.6.9RockSniffer/Configuration/OutputSettings.cs— addedpath.txtdefault output entryaddons/_deps/sniffer-poller.js— STATE_* constants changed from integers to stringsaddons/vocals/script.js— full rewrite for karaoke-style line slide, tuned values applied, comments trimmed
v0.6.8
RockSniffer v0.6.8
Summary
v0.6.8 delivers a long-awaited unlock: reliable arrangement-ID resolution in Nonstop Play, plus the downstream improvements that the unlock enabled - full playthrough tracking for Nonstop, a redesigned mode field that classifies every Rocksmith gameStage (not just in-song states), and a small set of defensive cleanups in dependent code.
Three categories of change, all shipping together:
- New PLAY_arrID memory chain: closes the Nonstop-Play arrangement-ID gap that has existed since the cross-reference logic was introduced.
modefield redesign: derived fromgameStageinstead of from note-data pointer resolution, with a much richer classification covering menu states, mode-specific tuners, Session Mode, Lessons, Guitarcade, and a contextual MENU bucket.- Playthrough Nonstop unblock: the
wasNonstopModegate added in v0.6.5 hotfix4 (which skipped all history/tracker writes for Nonstop plays) is removed now that PLAY_arrID makes the data trustworthy.
1. PLAY_arrID chain – reliable Nonstop arrangement ID
What it fixes
Pre-v0.6.8, the arrangement_hash memory chain didn't populate during Nonstop Play. Songs played in NSP had blank arrangement IDs in their downstream readouts, which:
- Caused playthrough_history rows to have empty arrangement_id columns
- Made it impossible to distinguish between regular and bonus/alternate arrangements (e.g., regular Bass vs. bonus Bass) – only Path-byte fallback was available, which encodes the type (Lead/Rhythm/Bass) but not the specific GUID
- Triggered the v0.6.5 hotfix4 gate that skipped persistent writes entirely for Nonstop plays
How it works
A new pointer chain – internally PLAY_arrID – reads the currently-loaded arrangement GUID from a separate memory location that does populate in Nonstop. The chain walks:
Rocksmith2014.exe + 0x00F5C5AC (Beta-base)
[+ 0x3080 for Remastered, + 0x4080 for LaP]
→ offsets [0xB0, 0x18, 0x4, 0x84, 0x20]
→ 16 raw bytes → Guid → 32-char uppercase hex string
This matches the format of PSARC arrangementID fields directly, so cross-reference against songDetails.arrangements[] is a straight string comparison.
Dispatch
The arrangement-ID read now dispatches by gameStage:
| gameStage | Chain used |
|---|---|
las_game, las_pause, nonstopplaygame, nsp_pause |
PLAY_arrID (new) |
sa_game, sa_pause |
Legacy arrangement_hash (unchanged) |
| All other states (menus, song-select, song-review) | Legacy arrangement_hash (unchanged – preserves pre-v0.6.8 behavior) |
gameStage is now read above the arrangement-ID block in DoReadout so the dispatch has the signal it needs.
Files changed
RockSnifferLib/RSHelpers/MemoryOffsets.cs– addedGetPlayArrIDPointer(RSEdition)(Beta/Remastered/LaP shifts)RockSnifferLib/RSHelpers/RSMemoryReader.cs– added privateReadPlayArrIDFromMemory(IntPtr)helper, reordered gameStage read, added dispatch
2. mode field redesign – gameStage-derived classification
What it fixes
Pre-v0.6.8, the readout.mode field was assigned inside ReadNoteData / ReadScoreAttackNoteData, meaning:
- NSP gameplay was misclassified as LEARNASONG because Nonstop reuses the LaS note-data subsystem internally (same magic-number-111000 struct shape). The enum had no
NONSTOPPLAYvalue to distinguish them, and even if it had, this code path couldn't have set it – both modes produced an identicalLearnASongNoteDataread. - All menu states were classified UNKNOWN because note-data pointers only populate during active gameplay. Mainmenu, song-select, song-review, options screens, the Score Attack hub, and every transition screen all came through as UNKNOWN.
- The classification was implicitly coupled to note-data pointer resolution, conflating two unrelated concerns ("what note data do I read?" vs. "what mode is the user in?").
How it works
mode is now derived from gameStage, which is the canonical signal for what the user is doing in the game (read from a static memory address since v0.6.6 – reliable across all states including menus, transitions, and Nonstop). A single classifier function DeriveModeFromGameStage does exact-match-then-prefix-match into the expanded enum.
Enum expansion
Five new values appended to RSMode. Integer values 0–3 preserved for any external integer-based serialization:
public enum RSMode
{
UNKNOWN, // 0
LEARNASONG, // 1
SCOREATTACK, // 2
MULTIPLAYER, // 3
NONSTOPPLAY, // 4 (new)
GUITARCADE, // 5 (new)
SESSION, // 6 (new)
LESSONS, // 7 (new)
MENU // 8 (new)
}Full mapping
| Mode | Exact gameStages | Prefix matches |
|---|---|---|
LEARNASONG |
learnasong, las_songs, las_options, las_tuner, las_game, las_pause, las_songreview |
– |
SCOREATTACK |
scoreattack, panel_bib, scoreattack_presongtuner, sa_game, sa_pause, sa_songreview |
– |
GUITARCADE |
gcpre, gcade, gcade_game, guitarcade_tuner |
gc_ |
NONSTOPPLAY |
nonstopplay, nsp_main, nonstopplayhub, nsp_tuner, nonstopplaygame, nsp_pause |
– |
SESSION |
– | sm_ |
LESSONS |
getuner, pregametuner |
ge_ |
MULTIPLAYER |
split_game |
mp_, duet_, h2h_ |
MENU |
titlescreen, profileselect, main, mainmenu, statsmenu, shop, contentpanelchord, sidelist |
tonedesigner |
UNKNOWN |
(anything else) | – |
Case-insensitive throughout – input is lowercased once at the top of the classifier.
Special case: bare tuner
The bare tuner gameStage fires when the universal tuner is accessed from any parent context (pause menu of any mode, main menu, Session Mode, etc.). It's distinct from the mode-specific tuners (las_tuner, nsp_tuner, scoreattack_presongtuner, guitarcade_tuner, getuner, pregametuner) which are classified under their parent mode.
Because the bare tuner is stateless from the gameStage alone, the prior mode is preserved: DoReadout skips the reclassification when it sees bare tuner, leaving readout.mode as whatever it was on the previous poll. Edge case: if the first observed gameStage after RockSniffer attaches is the bare tuner with no prior state, mode falls through to UNKNOWN and resyncs the moment the user navigates away.
Caller-side impacts
Sniffer.cs:1057(end-of-song log enrichment): Compatible. At LogSongEnd time, gameStage is still the in-song state (transitions to menus happen after the snapshot), so the SA-stat appendage fires correctly when expected.PlaythroughHistory.cs:250 / :327(SA column population): Compatible. Already pattern-matched againstnoteData is ScoreAttackNoteData, which remains the correct disambiguator under the decoupled mode/note-data contract.PlaythroughHistory.cs:289 / :350(game_mode column): Behavior change documented below.DiscordRPCHandler.cs:127(SA score appendage on Discord RPC): Defensive guard added (see Section 3).
game_mode column behavior change
Both the SQLite playthrough_history table's game_mode column and its CSV equivalent are populated by readout.mode.ToString(). Going forward:
- Nonstop plays correctly write
"NONSTOPPLAY"(rather than the pre-v0.6.8 incidental"LEARNASONG"that resulted from NSP reusing the LaS note-data subsystem). - Any other newly-classified stage writes its appropriate label (
"GUITARCADE","SESSION","LESSONS","MENU"). - Existing rows in the database are unchanged – only new rows reflect the corrected classification.
If you query for Nonstop plays, the predicate is now WHERE game_mode = 'NONSTOPPLAY' going forward, while historical rows still match WHERE game_mode = 'LEARNASONG' (mixed with legitimate LaS plays – there's no way to retroactively disambiguate without re-running the readout against the original memory state).
Files changed
RockSnifferLib/RSHelpers/RSMode.cs– enum expansion + doc commentsRockSnifferLib/RSHelpers/RSMemoryReader.cs– addedDeriveModeFromGameStagestatic helper, wired intoDoReadoutwith the bare-tuner persistence wrapper, removed the three legacyreadout.mode = …writes (LaS branch, SA branch, UNKNOWN fallback)
3. Playthrough Nonstop unblock
What it fixes
The wasNonstopMode gate added in v0.6.5 hotfix4 skipped all playthrough_history (CSV + SQLite) and playthrough_tracker (per-attempt) writes for songs played in Nonstop Play. The gate was added because the missing arrangement_hash chain and the bonus/alternate ambiguity made the data untrustworthy – historic records should be trustworthy or absent.
PLAY_arrID resolves both stated reasons for the gate:
- Arrangement-ID memory pointer now populates in Nonstop
- Per-arrangement GUID directly distinguishes regular from bonus and alternate arrangements
So the gate is lifted. Nonstop plays now save on equal footing with LaS and SA plays.
What this changes for users
- Nonstop plays now appear in
playthrough_history(SQLite + CSV) - Per-attempt stats in
playthrough_tracker(the per-section/per-phrase storage backing addons) now persist across Nonstop sessions - Per-song "previous best" lookups work correctly for songs whose only attempts have been in Nonstop
What this preserves
wasNonstopModeis still set on theOnActualSongStart/OnActualSongEndevent args bySniffer.cs– it just no longer gates writes. Any future or external consumer that wants the contextual flag still has it.- All defensive null/empty checks remain in place (no-currentAttempt guards, no-arrangement guards, etc.).
Files changed
RockSniffer/History/PlaythroughHistory.cs– early-return gate atOnActualSongEnd...
v0.6.7
v0.6.7 - flag-driven pause detection, state machine reliability, readable JSON enums
Pause and resume are detected on the first poll across all song modes and Guitarcade minigames. New
pauseMenuMode/isPausedfields expose Rocksmith's overlay state to addons. The state machine no longer gets stuck on RockSniffer attach, Rocksmith restart, or song-load backout. JSON output now showscurrentStateandmodeas readable strings.
Highlights
- Pause detection is now first-poll instant across all three song modes and Guitarcade minigames. The state machine no longer waits for consecutive timer-stalled polls before flipping to
SONG_PAUSED, dropping detection latency from roughly 100–300 ms to a single poll. - Resume detection is also first-poll instant, with the tuner-from-pause sub-flow handled cleanly. Entering the tuner from the pause menu no longer triggers a spurious "resume" event, and exiting a song from the pause menu no longer briefly flickers through
SONG_PLAYINGbefore reachingIN_MENUS. - Pause is now correctly detected near the end of a song. The previous heuristic had to suppress pause detection in the final 2 seconds to avoid false positives from end-of-song timer stalls. That guard is gone; false positives are no longer possible.
- Pause is now correctly detected in Guitarcade minigames. The previous timer-stall heuristic was scoped to song-timer behavior; Guitarcade games don't have a song timer in the conventional sense, so pause was effectively invisible.
- The state machine no longer gets stuck in
SONG_STARTING. Three scenarios that previously left the state machine parked inSONG_STARTINGindefinitely now recover cleanly: RockSniffer attaching to an already-running Rocksmith, Rocksmith restarting while RockSniffer is running, and the user pressing Esc on a song's loading screen before gameplay begins. - JSON output is now human-readable:
currentStateandmodeserialize as enum names (e.g."SONG_PAUSED","SCOREATTACK") instead of underlying integers. Affects the debug overlay and any addon consuming the JSON readout. - New addon-accessible fields:
pauseMenuMode(raw byte, 0/1/2) andisPaused(derived bool) on the memory readout, both auto-serialized into the addon JSON.
What changed in pause detection
SnifferState's SONG_PLAYING ↔ SONG_PAUSED transitions now read Rocksmith's pause-menu mode byte directly from a .data-section static at Rocksmith2014.exe+0xF5F5FC (Remastered). The variable encodes blocking-overlay state:
| Value | Meaning | Where observed |
|---|---|---|
0 |
No blocking overlay | Active gameplay, menus, loading, song review |
1 |
Sub-overlay active | Tuner accessed from the pause menu; Tools-menu sub-options |
2 |
Top-level blocking overlay | Pause menu, Mixer, Tools menu (from main menu), restart-confirmation prompts |
The state machine treats any non-zero value as "user is in a pause sub-flow." This handles the tuner-from-pause edge case without any special-case logic: the engine transitions cleanly between 2 → 1 → 2 when entering and exiting the tuner, never dropping to 0, so SnifferState.SONG_PAUSED is preserved correctly throughout.
Pause-exit (user hits Exit from the pause menu) is also handled cleanly: a pauseTimerSnapshot captured at pause-entry is compared against the current songTimer in the resume condition, so the moment Exit clears pauseMenuMode doesn't briefly flip the state to SONG_PLAYING while songTimer is still frozen at the pause-point.
Cross-mode validated for Score Attack, Learn-A-Song, Nonstop Play, and Guitarcade minigames. Verified to survive game relaunch as a true static (no warmup gate, no per-session memory shifting).
State-machine reliability improvements
Two additional guards prevent the currentState field from getting stuck in unexpected states:
-
IN_MENUS → SONG_SELECTEDis gated ongameStage. The transition previously fired onsongTimer != 0alone, which let transient memory garbage at process startup or Rocksmith restart march the state machine forward throughSONG_SELECTEDand intoSONG_STARTINGwithout the user actually being in a song. The new gate requiresgameStageto be one oflas_game,sa_game,nonstopplaygame,las_pause,sa_pause, ornsp_pausebefore the transition can fire. -
SONG_STARTINGhas an escape hatch back toIN_MENUS. If the user backs out of a song's loading screen beforesongTimerever advances, the state machine previously had no exit and would park inSONG_STARTINGindefinitely. The new escape branch fires whengameStagetransitions to a known menu/tuner/songreview state (mainmenu,gcpre,learnasong,scoreattack,nonstopplay,nsp_main,las_songs,las_options,las_tuner,nsp_tuner,las_songreview,sa_songreview,panel_bib,shop, orgcade), unwinding cleanly back toIN_MENUS.
Both guards deliberately exclude *_pause gameStages, which are sticky after the first pause during any song and would otherwise produce false signals.
Readable JSON enums
Two enum fields exposed to addons now serialize as their string names rather than underlying integers:
| Field | Before v0.6.7 | After v0.6.7 |
|---|---|---|
currentState (top-level) |
"currentState": 6 |
"currentState": "SONG_PAUSED" |
memoryReadout.mode |
"mode": 2 |
"mode": "SCOREATTACK" |
This makes the debug overlay output legible at a glance and removes the need for addons to hardcode integer-to-name mappings.
Breaking-change note: any third-party addon that compared currentState or mode to integer literals (e.g. if (data.currentState === 6)) will break. None of the bundled addons in this repository do this. Affected third-party addons need a one-line fix to compare against string names instead.
Why both pause signals coexist on the readout
pauseMenuMode reflects Rocksmith's engine state directly; including the fact that the main menu's Tools overlay (accessible via SPACE while not in a song) also reads as 2, even though it has nothing to do with mid-song pause. Addons that want to filter for "user is paused during an active song specifically" should combine pauseMenuMode != 0 with a SnifferState check (e.g. currentState is SONG_PLAYING or SONG_PAUSED).
isPaused is the raw pauseMenuMode != 0 test exposed as a convenience boolean. For addons that don't care about the Tools-menu distinction, it's a one-bit answer.
Both fields propagate every poll regardless of songTimer, so addons consuming prevReadout see fresh pause state during transitional windows (loading screens, song boundaries).
Internal cleanup
The timer-stall heuristic that previously drove pause detection has been retired entirely:
STALL_THRESHOLD,STALL_EPSILON, andEND_OF_SONG_PAUSE_GUARDconstants removedstallCountfield removed- Per-poll stall-tracking arithmetic in the playing loop removed
- Six
stallCount = 0;reset statements across the file removed lastObservedTimerfield retained for diagnostic logging only
The pause-entry / pause-exit logic in Sniffer.UpdateState() is now flag-driven with explicit timer-snapshot disambiguation, replacing the old multi-poll stall accumulator.
Compatibility
- Verified on: Rocksmith 2014 Remastered. Cross-mode tested: Score Attack, Learn-A-Song, Nonstop Play, Guitarcade minigames. Process-relaunch tested.
- Beta and Learn-And-Play builds: memory offset back-derived using the standard
+0x3080/+0x4080shift convention (now consistent across twelve mapped addresses). Untested on those builds. If pause detection misbehaves on Beta or LaP (no pause ever registered, or constant pause), the new offset is the most likely suspect. - Existing addons: consumers of
SnifferState.SONG_PAUSEDvia the event API continue working unchanged; they get faster pause transitions automatically. Consumers reading enum values from JSON as strings work correctly. Consumers reading enum values from JSON as integers need a one-line fix.
Discovery credit
The pause-menu mode byte was discovered using Cheat Engine memory inspection in the neighborhood of two named candidate addresses provided by kokolihapihvi (upstream RockSniffer maintainer): MustBlockInputsDueToPauseMenu and EnablePauseMenu. Both turned out to be dead in the current Remastered build (consistently zero across all states), but the neighborhood inspection they prompted led directly to this find. Thanks Koko.
v0.6.6
v0.6.6 - gameStage memory reader migrated to static address
Eliminates junk values in Score Attack pre-song menus, surfaces three previously-invisible game stages, and lays the groundwork for flag-driven pause detection.
Highlights
game_stage.txtno longer reports junk strings in Score Attack pre-song menus (song select, song options, tuner). Previously these screens reported garbled file path fragments and other unrelated buffer contents.- Three new game stages now visible for the first time:
las_pause,nsp_pause,nsp_tuner. These exist in Rocksmith but were silently dropped by the previous reader. - No changes to addons, Discord RPC, song logging, or Nonstop Play transition handling. Existing consumers continue working unchanged.
What changed
The previous reader walked a four-hop pointer chain ([0x18, 0x18, 0xC, 0x14] from a base pointer) into a transient per-screen UI struct. That struct gets reused or torn down across game state changes, which is why menu states returned garbage and pause stages for LaS / NSP were never seen -the chain simply wasn't pointing at the right cell when those stages were active.
gameStage is now read directly from the canonical .data-section buffer at Rocksmith2014.exe+0xF5F7C9 (Remastered). This is the cell Rocksmith's UI code writes for the current displayed stage; the same place that powers the in-game UI text. Tracks correctly across all observed states.
Behavior worth knowing
Rocksmith does not update the gameStage cell on pause → resume or pause → restart transitions. The cell continues to read *_pause until the user navigates back to a menu or starts a different song. This is engine behavior, not a reader bug.
Addons that need actual play/pause state should consult game_state (the SnifferState) rather than parsing gameStage strings– game_state derives play/pause from song-timer behavior, not gameStage strings, and handles this correctly.
Compatibility
- Verified on: Rocksmith 2014 Remastered.
- Beta and Learn-And-Play builds: memory offsets back-derived using the standard
+0x3080/+0x4080Beta→Remastered / Beta→LaP shift convention. Untested on those builds. If gameStage misbehaves on Beta or LaP (always empty, always garbage), the new offset is the most likely suspect.
Also in this release
- Line-ending hygiene. Both
RockSnifferandRockSnifferLibrepos now have.gitattributespinning LF on checkout regardless of platform. Eliminates CRLF/LF mismatch warnings when extracting source archives between Linux and Windows. No effect on the built binary.
Discovery credit
The static gameStage buffer was discovered using Cheat Engine string-scan for known stage names (gcpre, sa_pause) with the game running, then narrowing the candidate addresses by which one round-tripped correctly across mode and menu transitions.
Acknowledgments
Thanks to kokolihapihvi (upstream RockSniffer maintainer) for cross-referencing the original reverse-engineering notes that helped narrow the search.
v0.6.5
RockSniffer PJ v0.6.5 - Path Memory Integration & Nonstop Support
This release adds Nonstop Play support to the addon overlay system, fixes several edge cases in song-start detection, and removes deprecated code and configuration that the new Path-based arrangement resolution makes unnecessary.
Headline Feature: Overlays Now Work in Nonstop Play
Previous versions could not resolve the active arrangement in Rocksmith's Nonstop Play mode. The arrangement-hash memory pointer that LaS and Score Attack rely on simply doesn't populate in Nonstop, so addons fell through to a fragile default-path heuristic and frequently rendered the wrong arrangement's sections, phrases, and tuning.
v0.6.5 introduces a new memory read for currentPath. the user's currently selected instrument path (Lead / Rhythm / Bass), read from a stable byte pointer that's populated from Rocksmith launch onward and works in every game stage including Nonstop. The currentPath field is now exposed in the standard addon JSON readout and is used by both the C# side and sniffer-poller.js as the primary fallback when the arrangement-hash pointer is unavailable.
In practice: section names, phrase iterations, and tuning name now render correctly during song-select and gameplay across Learn A Song, Score Attack, and Nonstop Play.
Memory Pointer Reference
Path is read with the following pointer chain (offsets [0x10, 0x1FC], byte read):
| Edition | Base Address |
|---|---|
| Beta | 0x00F5C4F0 |
| Remastered | 0x00F5F570 |
| Learn_And_Play | 0x00F60570 |
Value mapping: 0x01 = Lead, 0x02 = Rhythm, 0x04 = Bass. Anything else resolves to empty (resolution falls through to the defensive heuristics).
The Beta and Learn_And_Play offsets follow the consistent +0x3080 / +0x4080 shift convention from the existing memory map. They are back-derived; only the Remastered base has been verified empirically. If you run Beta or LaP and observe overlay arrangement issues, please report it.
Bug Fixes
Song-start detection (hotfixes 1-4)
- Empty-shell guard: prevents song-start from firing on an in-flight, partially-populated CDLC details object during cache resolution.
- Replay/restart handling: replaying the same song or restarting from pause no longer causes the song-start fire-once guard to silently swallow the second start event.
- gameStage transition edge cases: state transitions through
nsp_main→nonstopplayhub→nonstopplaygamenow correctly trigger song-start logging.
Arrangement resolution (hotfix 5 series)
- 5.0 : Path-based resolution introduced in both the C# (
Sniffer.cs) and JS (sniffer-poller.js) sides as the primary fallback when arrangementID matching fails. - 5.1 : Restored first-match-wins semantics in the Path filter step. An earlier count-and-only-pick-if-one approach left sections and phrases unrendered during song-select for any song with multiple arrangements of the same type. Behavior now matches the legacy resolution semantics that users expect.
- 5.2 : Fixed the Remastered base address. The pointer was being double-shifted by
+0x3080, causingcurrentPathto read from the wrong memory cell. Now correctly resolves on Remastered.
Nonstop Play history gating
Songs played in Nonstop Play are now excluded from CSV and SQLite playthrough history, and from the per-attempt JSON tracker. Reason: even with Path-based resolution, we cannot reliably distinguish between, say, a regular Bass arrangement and a bonus Bass arrangement, or between Lead and an alt-Lead chart. Writing potentially-wrong arrangement tags into the user's permanent history is worse than not writing. Historic records should be trustworthy or absent. Nonstop sessions still log to sniffer.log, fire Discord RPC events, and update live overlays normally; only the persistent history layer is skipped.
This is expected to remain in place until a Nonstop-compatible arrangementID memory pointer is found.
Warning noise reduction
The "Could not resolve arrangement at song start" warning is now suppressed when the song was started in Nonstop Play. In Nonstop, Path-based fallback is the expected resolution path, not a degradation, so the warning was firing on every Nonstop song. The warning is still emitted in Learn A Song / Score Attack / other modes, where it remains a useful safety signal for transient timing races or genuine bugs.
Removed (Deprecated)
The following items are no longer used as of v0.6.5. None of these are part of the user-facing config or workflow; mention is for completeness in case anyone has forked the addons or scripted around the previous internals.
| Item | Replaced by |
|---|---|
addons/config-ui.js |
currentPath from memory readout (consumed directly by addons) |
addons/_deps/sniffer_state.json |
(removed; was used to persist lastResolvedPath across sessions) |
defaultArrangementType setting |
currentPath (real-time memory read) |
SnifferRuntimeState class |
(removed; sniffer_state.json no longer written) |
Internal lastResolvedPath field |
(removed) |
prevPath / prev-path heuristic |
(removed from C# fallback chain; prevArrangement retained) |
Effect on existing installs
- An existing
sniffer_state.jsonbecomes orphaned and can be deleted manually. RockSniffer will not read or write it. - A
defaultArrangementTypesetting insniffer.jsonwill be ignored. It will be silently stripped on the next config write, which is harmless. - Custom addons that imported
defaultPathfromconfig-ui.jswill throw aReferenceError. The fix is to readthis._prevdata.memoryReadout.currentPath(in poller-style addons) orthis.readout.currentPath(in Vue-style addons).
Internal Changes
- Net code reduction: −173 lines across the Lib (
Sniffer.csshed 76 lines;SnifferSettings.csshed 11;SnifferRuntimeState.csremoved entirely at 86 lines). - Resolution chain in
LogSongStartIfPossiblesimplified from 6 steps to 4: arrangementID → Path filter → single-playable-arrangement heuristic → only-arrangement-on-song heuristic → unresolved. - All 13 shipped addon HTMLs had their
<script src="../config-ui.js">line removed.
Known Limitations
- Nonstop arrangementID resolution is still unsolved. Path resolves the type (Bass / Lead / Rhythm) but cannot disambiguate within a type (regular vs bonus, primary vs alt). Until a Nonstop-compatible arrangementID pointer is identified in memory, Nonstop runs cannot be written to history.
- Beta and Learn_And_Play Path pointers are unverified. Offsets are back-derived from the consistent edition-shift convention used elsewhere in the memory map. Empirical confirmation welcome.
- Pause detection occassionally sticks in Score Attack mode; inconsistent and requires further diagnosis.
v0.6.4
v0.6.4 quick update
-
Merged RocksnifferLib with upstream commits to fully bring in line with main Rocksniffer fork, in preparation for eventual merge into main fork
-
Updated Rocksniffer repo to use latest RocksnifferLib submodule
v0.6.3
v0.6.3 Hotfix
Fixes a JavaScript scoping bug in addon overlay scripts introduced in v0.6.2.
Variables using var redeclaration as reassignment were incorrectly converted
to let/const, causing overlays (Arcade_v1_LaS, Arcade_v1_SA, and others)
to fail with "Cannot access before initialization" errors.
All addon script.js files are corrected. No C# or RockSnifferLib changes.
v0.6.2
RockSniffer v0.6.2 Release Notes
Improvements
- Output file caching — Output files are only written to disk when their content has changed, eliminating redundant file I/O during idle periods.
sniffSnifftoggle — The random*sniff sniff*console message can be disabled inconfig/output.json.- JavaScript modernization — All addon scripts updated from
vartolet/constwith proper scoping.formatTimerfunctions refactored with early return pattern. - GDPR compliance — All Google Fonts imports removed from addon HTML files; fonts are loaded locally.
- Addon bugfixes — Fixed phrase/section meter height calculations when switching arrangement paths in multiple overlay addons.
Configuration Changes
New fields in config/output.json:
eventLogMode— now an enum:"Disabled","Enabled", or"Legacy"(previously a freeform string)sniffSniff— boolean, defaults totruesqliteHistoryPath— default changed from"playthrough_history.db"to"output/playthrough_history.db"(existing configs are unaffected)csvHistoryPath— default changed from"playthrough_history.csv"to"output/playthrough_history.csv"(existing configs are unaffected)
Upgrade Notes
- PSARC cache must be cleared after upgrading for the bass tuning truncation feature to take effect on previously cached songs. Delete
cache.sqliteand restart RockSniffer. - Existing
output.jsonconfigurations are fully backward compatible. New defaults only apply to fresh installs.