Skip to content

Commit

Permalink
Patches for Resident Evil 2/3 audio issues
Browse files Browse the repository at this point in the history
These games are erroneously zeroing buffers before they can be fully copied to ARAM by DMA. The responsible memset() calls are followed by a call to DVDRead() which issues dcbi instructions that effectively cancel the memset() on real hardware. Because Dolphin lacks dcache emulation, the effects of the memset() calls are observed, which causes missing audio.

In a comment on the original bug, phire noted that the issue can be corrected by simply nop'ing out the offending memset() calls. Because the games dynamically load different .rel executables based on the character and/or language, the addresses of these calls can vary.

To deal generally with the problem of dynamic code being loaded to fixed, known addresses, the patch engine is extended to support conditional patches which require a match against a known value. This sort of thing is already achievable with Action Replay codes, but their use depends on enabling cheats globally in Dolphin, which is not a prerequisite shared by patches.

Patches are included for every region, character, and language combination.

The end result is an approximation of the games' behavior on real hardware without the associated complexity of proper dcache emulation.

https://bugs.dolphin-emu.org/issues/9840
  • Loading branch information
smurf3tte committed Dec 4, 2020
1 parent a34823d commit 9ea0e22
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 3 deletions.
11 changes: 11 additions & 0 deletions Data/Sys/GameSettings/GHAE08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# GHAE08 - Resident Evil 2 / Biohazard 2

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# main.dol
0x800339E4:dword:0x60000000
# leon.rel
0x8055ACBC:dword:0x60000000:0x4BAA8445
# claire.rel
0x8055AB54:dword:0x60000000:0x4BAA85AD
11 changes: 11 additions & 0 deletions Data/Sys/GameSettings/GHAJ08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# GHAJ08 - Biohazard 2

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# main.dol
0x80065FFC:dword:0x60000000
# leon.rel
0x805C5CC4:dword:0x60000000:0x4BA3D43D
# claire.rel
0x805C5BFC:dword:0x60000000:0x4BA3D505
27 changes: 27 additions & 0 deletions Data/Sys/GameSettings/GHAP08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# GHAP08 - Resident Evil 2 / Biohazard 2

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# main.dol
0x80033D60:dword:0x60000000
# leon.rel
0x8055C5F8:dword:0x60000000:0x4BAA6B09
# claire.rel
0x8055C490:dword:0x60000000:0x4BAA6C71
# leon_g.rel
0x8055C3B8:dword:0x60000000:0x4BAA6D49
# claire_g.rel
0x8055C328:dword:0x60000000:0x4BAA6DD9
# leon_f.rel
0x8055D188:dword:0x60000000:0x4BAA5F79
# claire_f.rel
0x8055D068:dword:0x60000000:0x4BAA6099
# leon_s.rel
0x8055D100:dword:0x60000000:0x4BAA6001
# claire_s.rel
0x8055D064:dword:0x60000000:0x4BAA609D
# leon_i.rel
0x8055CFDC:dword:0x60000000:0x4BAA6125
# claire_i.rel
0x8055CEBC:dword:0x60000000:0x4BAA6245
7 changes: 7 additions & 0 deletions Data/Sys/GameSettings/GLEE08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# GLEE08 - Resident Evil 3: Nemesis

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# main.dol
0x80150E94:dword:0x60000000
7 changes: 7 additions & 0 deletions Data/Sys/GameSettings/GLEJ08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# GLEJ08 - BioHazard 3: Last Escape

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# main.dol
0x8015110C:dword:0x60000000
15 changes: 15 additions & 0 deletions Data/Sys/GameSettings/GLEP08.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# GLEP08 - Resident Evil 3: Nemesis

[OnFrame]
# Skip memset call that is followed by dcbi, simulating dcache behavior
$Fix audio issues
# eng.rel
0x8058C174:dword:0x60000000:0x4BA76F8D
# ger.rel
0x8058CE40:dword:0x60000000:0x4BA762C1
# fra.rel
0x8058D03C:dword:0x60000000:0x4BA760C5
# spa.rel
0x8058D024:dword:0x60000000:0x4BA760DD
# ita.rel
0x8058CEA4:dword:0x60000000:0x4BA7625D
27 changes: 27 additions & 0 deletions Source/Core/Core/PatchEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ void LoadPatchSection(const std::string& section, std::vector<Patch>& patches, I
bool success = true;
success &= TryParse(items[0], &pE.address);
success &= TryParse(items[2], &pE.value);
if (items.size() >= 4)
{
success &= TryParse(items[3], &pE.comparand);
pE.conditional = true;
}

const auto iter =
std::find(s_patch_type_strings.begin(), s_patch_type_strings.end(), items[1]);
Expand Down Expand Up @@ -191,15 +196,37 @@ static void ApplyPatches(const std::vector<Patch>& patches)
{
u32 addr = entry.address;
u32 value = entry.value;
u32 comparand = entry.comparand;
switch (entry.type)
{
case PatchType::Patch8Bit:
if (entry.conditional)
{
if (PowerPC::HostRead_U8(addr) != static_cast<u8>(comparand))
{
break;
}
}
PowerPC::HostWrite_U8(static_cast<u8>(value), addr);
break;
case PatchType::Patch16Bit:
if (entry.conditional)
{
if (PowerPC::HostRead_U16(addr) != static_cast<u16>(comparand))
{
break;
}
}
PowerPC::HostWrite_U16(static_cast<u16>(value), addr);
break;
case PatchType::Patch32Bit:
if (entry.conditional)
{
if (PowerPC::HostRead_U32(addr) != comparand)
{
break;
}
}
PowerPC::HostWrite_U32(value, addr);
break;
default:
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/Core/PatchEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ struct PatchEntry
PatchType type = PatchType::Patch8Bit;
u32 address = 0;
u32 value = 0;
u32 comparand = 0;
bool conditional = false;
};

struct Patch
Expand Down
45 changes: 44 additions & 1 deletion Source/Core/DolphinQt/Config/NewPatchDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "DolphinQt/Config/NewPatchDialog.h"

#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
Expand Down Expand Up @@ -95,6 +96,12 @@ static bool PatchEq(const PatchEngine::PatchEntry& a, const PatchEngine::PatchEn
if (a.value != b.value)
return false;

if (a.comparand != b.comparand)
return false;

if (a.conditional != b.conditional)
return false;

return true;
}

Expand All @@ -117,17 +124,25 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry)

auto* offset = new QLineEdit;
auto* value = new QLineEdit;
auto* comparand = new QLineEdit;

m_edits.push_back(offset);
m_edits.push_back(value);
m_edits.push_back(comparand);

auto* conditional = new QCheckBox(tr("Conditional"));
auto* comparand_label = new QLabel(tr("Comparand:"));

auto* layout = new QGridLayout;
layout->addWidget(type, 0, 0, 1, -1);
layout->addWidget(new QLabel(tr("Offset:")), 1, 0);
layout->addWidget(offset, 1, 1);
layout->addWidget(new QLabel(tr("Value:")), 2, 0);
layout->addWidget(value, 2, 1);
layout->addWidget(remove, 3, 0, 1, -1);
layout->addWidget(conditional, 3, 0, 1, -1);
layout->addWidget(comparand_label, 4, 0);
layout->addWidget(comparand, 4, 1);
layout->addWidget(remove, 5, 0, 1, -1);
box->setLayout(layout);

connect(offset, qOverload<const QString&>(&QLineEdit::textEdited),
Expand Down Expand Up @@ -164,6 +179,23 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry)
value->setPalette(palette);
});

connect(comparand, qOverload<const QString&>(&QLineEdit::textEdited),
[&entry, comparand](const QString& text) {
bool okay;
entry.comparand = text.toUInt(&okay, 16);

QFont font;
QPalette palette;

font.setBold(!okay);

if (!okay)
palette.setColor(QPalette::Text, Qt::red);

comparand->setFont(font);
comparand->setPalette(palette);
});

connect(remove, &QPushButton::clicked, [this, box, entry] {
if (m_patch.entries.size() > 1)
{
Expand Down Expand Up @@ -195,8 +227,19 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry)
word->setChecked(entry.type == PatchEngine::PatchType::Patch16Bit);
dword->setChecked(entry.type == PatchEngine::PatchType::Patch32Bit);

connect(conditional, &QCheckBox::toggled, [&entry, comparand_label, comparand](bool checked) {
entry.conditional = checked;
comparand_label->setVisible(checked);
comparand->setVisible(checked);
});

conditional->setChecked(entry.conditional);
comparand_label->setVisible(entry.conditional);
comparand->setVisible(entry.conditional);

offset->setText(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0')));
value->setText(QStringLiteral("%1").arg(entry.value, 8, 16, QLatin1Char('0')));
comparand->setText(QStringLiteral("%1").arg(entry.comparand, 8, 16, QLatin1Char('0')));

return box;
}
Expand Down
13 changes: 11 additions & 2 deletions Source/Core/DolphinQt/Config/PatchesWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,17 @@ void PatchesWidget::SavePatches()

for (const auto& entry : patch.entries)
{
lines.push_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address,
PatchEngine::PatchTypeAsString(entry.type), entry.value));
if (!entry.conditional)
{
lines.push_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address,
PatchEngine::PatchTypeAsString(entry.type), entry.value));
}
else
{
lines.push_back(StringFromFormat("0x%08X:%s:0x%08X:0x%08X", entry.address,
PatchEngine::PatchTypeAsString(entry.type), entry.value,
entry.comparand));
}
}
}

Expand Down

0 comments on commit 9ea0e22

Please sign in to comment.