Entering a folder in the browser from the screen of a Kontrol S8, S5 or D2 crashes Traktor
with an access violation. The same navigation with the mouse in the Traktor window never fails.
Users have been reporting this since September 2024.
I spent a couple of nights on it and found the exact root cause: the faulting instruction, the
missing validation, and a one-byte change that stops the crash.
I tried to open a support ticket with this and could not find where to file one, so I am posting
it here instead. If someone from NI can point me to the right channel, or forward this to the
Traktor engineers, I am happy to send everything — crash dumps, register state, disassembly.
Posting it publicly anyway so it is on record and other S8/S5/D2 users can at least work around it.
Affected: 4.2.0, 4.4.2 and 4.5.1.21 — every Traktor 4 version I tested.
Not affected: Traktor Pro 3.11.1, which ships the same QML and the same call.
Deterministic repro (5 minutes)
The crash only happens in folders that mix subfolders and loose audio files. The exact rule,
verified on three independent folders:
In a folder with
M subfolders
and
N loose tracks
, you can only enter the
first N
subfolders
. Entering subfolder N+1 crashes Traktor.
So:
- Create a folder with 5 subfolders and 1 audio file in it.
- Open it in the browser from an S8/S5/D2 screen.
- Enter the first subfolder — works.
- Go back, enter the second subfolder — crash.
Folders with only tracks, or only subfolders, never fail. That is why it looks intermittent.
Crash signature
Identical across 17 dumps on 4.5.1.21:
Exception : 0xC0000005 ACCESS_VIOLATION (read)
Reported addr : 0xFFFFFFFFFFFFFFFF
Module : Traktor Pro 4.exe + 0xA709CD
Same crash at +0xA6235D on 4.4.2 and +0xA461AD on 4.2.0 — the offset moves with the build,
the function is the same. The 0xFFFFFFFFFFFFFFFF is misleading: it is what Windows reports for
a non-canonical address, not a dereference of -1.
Root cause
An index-translation function returns -1, the caller never checks it, and the bounds check
that follows is signed.
- The translator at RVA
0xA6FEC0 maps an index from one list to another and returns -1 for
"not found". That function is fine. - The caller at
0xBB62C9 stores the result without checking it:
0xBB630E: call 0xA6FEC0 ; translate the index
0xBB6313: mov ebp, eax ; <-- stored without testing for -1
- The bounds check is signed, so
-1 sails through:
0xBB64F2: cmp ebp, eax ; index vs element count
0xBB64F4: jge +0x176 ; -1 < count, so it does NOT take the exit
0xBB6527: movsxd rax, ebp ; -1 sign-extended
0xBB6538: mov r8, [rbx+rax*8] ; reads 8 bytes BEFORE the array
0xBB6542: call 0xA709A0
[rbx-8] is adjacent memory. Read as a pointer it gives a non-canonical value, which the
destination function only validates against NULL (test r8,r8), so it passes, and
mov rdx,[r8+0x40] faults at 0xA709CD.
Evidence: EBP = 0xFFFFFFFF in 17 of 17 crash dumps. No exceptions. R8 differs every time
but is always non-canonical, which is consistent with reading memory next to the array rather
than with a fixed corrupt value.
The reason the count in step 3 does not match is that it comes from a different list than the
one the index belongs to — in 0xA6FF55 the node index is compared against the number of tracks.
That is where the "first N subfolders" rule comes from.
The fix
Either check the return value:
int index = translateIndex(obj, savedIndex);
if (index < 0) return; // this is what is missing
Or use an unsigned comparison in the bounds check, which covers negatives and overflows at once:
if ((unsigned)index >= (unsigned)count) return;
Verified
I changed the jump at 0xBB64F4 from jge to jae — one byte, 0x8D to 0x83, which is
exactly the second option above.
Result: zero crashes browsing intensively through the same folders that used to kill it every
few entries. The residual behaviour is that at the point where it used to crash, Traktor discards
the operation and loads a file instead of entering the folder. Not ideal, but it is the correct
response to an invalid index, and it does not take the application down.
There is still a state bug underneath — the stored index goes stale while navigating, which is
why the translation fails in the first place. That one is beyond what I can analyse from outside,
but it is probably the real origin.
Why this only happens on controller screens
browser.enterNode() is called from exactly one place in the whole QML tree:
Resources64/qml/Screens/S8/Templates/Browser/BrowserView.qml. That tree is used only by the S8,
S5 and D2. The Traktor window takes a different path — hence mouse navigation is unaffected.
Traktor Pro 3.11.1 ships the same file with an identical call and the same arguments, and does
not crash. The QML layer is not at fault.
Impact
Every S8, S5 and D2 owner on Traktor 4. Browsing is a core operation during a set — a crash there
means dead air. Previous reports:
- https://community.native-instruments.com/discussion/36160/traktor-pro-4-crashes-when-browsing-with-traktor-s8-kontroller
- https://community.native-instruments.com/discussion/35238/my-traktor-pro-4-freezes
- https://community.native-instruments.com/discussion/36267/traktor-pro-4-and-kontrol-s8-not-compatible
Workaround without patching anything
Keep loose audio files out of folders that also contain subfolders. Folders with only tracks, or
only subfolders, never trigger it.
I have the 17 crash dumps from 4.5.1.21 plus dumps from 4.4.2 and 4.2.0, with register state and
disassembly, available to anyone at NI who wants them.