From Nathan's crash dump: an access violation reading 8093e920, fourteen minutes into a session, on 4.12.115. rpl4opt!PatchLevelOfDetail::SetupPatch+0xbb rpl4opt!Static3DPatchSource::StartImplementation+0x50 rpl4opt!AudioRenderer::ExecuteBackground+0x9e The faulting instruction is g_buffers[index] with index = 0x20000000 - 536 million - and the array base in eax at 0093e920, which is exactly the address it died on. So the index was garbage, and the dump says where the garbage came from: the stack slot holding info.bufferIndex. PRESET_getSampleInfo builds a SAMPLEINFO to return when it is asked for a zone the preset does not have. It sets chan, file, implemented and loop - and not bufferIndex. Every caller tests bufferIndex >= 0 before using it, so "no such zone" was meant to be rejected there; instead the test read whatever was on the stack, and passed whenever that happened to be positive. AL_getBuffer then indexed the array with it, unchecked. Why it asked for a zone that is not there: the loop runs to sourceSet.count, which was fixed when the audio source was built, from whichever level of detail was selected at the time. SetDistance re-picks the level of detail by distance on the line immediately before SetupPatch runs, and the zone counts across the recovered banks are nothing like uniform - of 200 presets, 46 have no zones at all, and the rest run 1 to 4. So a sound that moved far enough to drop to a quieter patch could ask that patch for a zone it never had. In the dump: count 3, died asking for zone 2. Fixed at all three levels, because any one of them alone would have held: the default carries bufferIndex = -1 so the existing guard works, AL_getBuffer returns AL_NONE rather than reading past its array, and SetupPatch asks for no more zones than the patch it is actually using has. Verified: the dump's own numbers reproduce arithmetically, and two full races run clean. The distance-dependent trigger itself was reasoned from the dump rather than reproduced here - it needs a sound to cross a level of detail boundary into a shorter patch - so the belt-and-braces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
54 lines
1.3 KiB
C++
54 lines
1.3 KiB
C++
#include "L4AUDLVL.h"
|
|
|
|
bool PRESET_isImplemented(int bank, int presetn)
|
|
{
|
|
PRESETINFO preset = allPresets[bank-1][presetn];
|
|
|
|
if (preset.sampleNum <= 0 || preset.sampleNum >= 5)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i=0; i < preset.sampleNum; i++)
|
|
{
|
|
if (preset.samples[i].implemented)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int PRESET_getNumSamples(int bank, int preset)
|
|
{
|
|
return allPresets[bank-1][preset].sampleNum;
|
|
}
|
|
|
|
SAMPLEINFO PRESET_getSampleInfo(int bank, int preset, int sampleInd)
|
|
{
|
|
SAMPLEINFO default;
|
|
default.chan = SampleChannel::CHANNEL_CENTER;
|
|
default.file = "";
|
|
default.implemented = false;
|
|
default.loop = SampleLoop::LoopAtWill;
|
|
//
|
|
// -1 = no buffer. Every caller tests bufferIndex >= 0 before using it as
|
|
// an index, and this one field was being left as whatever was on the
|
|
// stack - so "this zone does not exist" read as a real buffer whenever
|
|
// the garbage happened to be positive, and indexed g_buffers with it.
|
|
//
|
|
default.bufferIndex = -1;
|
|
|
|
if (sampleInd < 0 || sampleInd >= allPresets[bank-1][preset].sampleNum)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return allPresets[bank-1][preset].samples[sampleInd];
|
|
}
|
|
|
|
void PRESET_setBufferIndex(int bank, int preset, int sampleInd, int index)
|
|
{
|
|
allPresets[bank-1][preset].samples[sampleInd].bufferIndex = index;
|
|
} |