Prevent out of memory errors when the game passes in an improper length value

HACK

In Luigi's Mansion Dark Moon in HLE audio, the game mysteriously passes
in an extremely large value for length, which without any checks, causes
HLE audio to allocate an extremely large buffer.

This value seemingly is caused by some other HLE audio feature is missing,
and Luigi's Mansion subtracts two values to get a length, without
checking for overflow first. This appears to be caused by an incorrect
HLE audio emulation, as its fixed entirely by only changing to LLE. As
such, further investigation is required, but in the meantime, completely
eating up our users RAM is unacceptable.
This commit is contained in:
James Rowe 2019-12-14 18:09:45 -07:00
parent 8b1738aeac
commit 87facaa2e2

View File

@ -171,20 +171,35 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
if (config.embedded_buffer_dirty) { if (config.embedded_buffer_dirty) {
config.embedded_buffer_dirty.Assign(0); config.embedded_buffer_dirty.Assign(0);
state.input_queue.emplace(Buffer{ // HACK
config.physical_address, // Luigi's Mansion Dark Moon configures the embedded buffer with an extremely large value
config.length, // for length, causing the Dequeue method to allocate a buffer of that size, eating up all
static_cast<u8>(config.adpcm_ps), // of the users RAM. It appears that the game is calculating the length of the sample by
{config.adpcm_yn[0], config.adpcm_yn[1]}, // using some value from the DSP and subtracting another value, which causes it to
static_cast<bool>(config.adpcm_dirty), // underflow. We need to investigate further into what value the game is reading from and
static_cast<bool>(config.is_looping), // fix that, but as a stop gap, we can just prevent these underflowed values from playing in
config.buffer_id, // the mean time
state.mono_or_stereo, if (static_cast<s32>(config.length) < 0) {
state.format, LOG_ERROR(Audio_DSP,
false, "Skipping embedded buffer sample! Game passed in improper value for length. "
play_position, "addr {:X} length {:X}",
false, config.physical_address, config.length);
}); } else {
state.input_queue.emplace(Buffer{
config.physical_address,
config.length,
static_cast<u8>(config.adpcm_ps),
{config.adpcm_yn[0], config.adpcm_yn[1]},
static_cast<bool>(config.adpcm_dirty),
static_cast<bool>(config.is_looping),
config.buffer_id,
state.mono_or_stereo,
state.format,
false,
play_position,
false,
});
}
LOG_TRACE(Audio_DSP, "enqueuing embedded addr={:#010x} len={} id={} start={}", LOG_TRACE(Audio_DSP, "enqueuing embedded addr={:#010x} len={} id={} start={}",
config.physical_address, config.length, config.buffer_id, config.physical_address, config.length, config.buffer_id,
static_cast<u32>(config.play_position)); static_cast<u32>(config.play_position));
@ -201,20 +216,27 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
for (std::size_t i = 0; i < 4; i++) { for (std::size_t i = 0; i < 4; i++) {
if (config.buffers_dirty & (1 << i)) { if (config.buffers_dirty & (1 << i)) {
const auto& b = config.buffers[i]; const auto& b = config.buffers[i];
state.input_queue.emplace(Buffer{ if (static_cast<s32>(b.length) < 0) {
b.physical_address, LOG_ERROR(Audio_DSP,
b.length, "Skipping buffer queue sample! Game passed in improper value for "
static_cast<u8>(b.adpcm_ps), "length. addr {:X} length {:X}",
{b.adpcm_yn[0], b.adpcm_yn[1]}, b.physical_address, b.length);
b.adpcm_dirty != 0, } else {
b.is_looping != 0, state.input_queue.emplace(Buffer{
b.buffer_id, b.physical_address,
state.mono_or_stereo, b.length,
state.format, static_cast<u8>(b.adpcm_ps),
true, {b.adpcm_yn[0], b.adpcm_yn[1]},
{}, // 0 in u32_dsp b.adpcm_dirty != 0,
false, b.is_looping != 0,
}); b.buffer_id,
state.mono_or_stereo,
state.format,
true,
{}, // 0 in u32_dsp
false,
});
}
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i, LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
b.physical_address, b.length, b.buffer_id); b.physical_address, b.length, b.buffer_id);
} }