Large diffs are not rendered by default.

@@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects:
[MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE)
- [ENet](http://enet.bespin.org/):
[MIT](http://enet.bespin.org/License.html)
- [expr](https://github.com/zserge/expr):
[MIT](https://github.com/zserge/expr/blob/master/LICENSE)
- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
@@ -449,6 +449,8 @@ add_library(core
PowerPC/CachedInterpreter/InterpreterBlockCache.h
PowerPC/ConditionRegister.cpp
PowerPC/ConditionRegister.h
PowerPC/Expression.cpp
PowerPC/Expression.h
PowerPC/Interpreter/ExceptionUtils.h
PowerPC/Interpreter/Interpreter_Branch.cpp
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
@@ -593,6 +595,7 @@ PUBLIC
cubeb
discio
enet
expr
inputcommon
${MBEDTLS_LIBRARIES}
pugixml
@@ -13,6 +13,7 @@
#include "Common/DebugInterface.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
#include "Core/PowerPC/Expression.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/MMU.h"

@@ -35,17 +36,16 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const
});
}

bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const
const TBreakPoint* BreakPoints::GetBreakpoint(u32 address) const
{
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
return bp.address == address && bp.break_on_hit;
auto bp = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
return bp.is_enabled && bp.address == address;
});
}

bool BreakPoints::IsBreakPointLogOnHit(u32 address) const
{
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(),
[address](const auto& bp) { return bp.address == address && bp.log_on_hit; });
if (bp == m_breakpoints.end() || !EvaluateCondition(bp->condition))
return nullptr;

return &*bp;
}

BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
@@ -57,10 +57,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
{
std::ostringstream ss;
ss.imbue(std::locale::classic());

ss << std::hex << bp.address << " " << (bp.is_enabled ? "n" : "")
<< (bp.log_on_hit ? "l" : "") << (bp.break_on_hit ? "b" : "");
bp_strings.push_back(ss.str());
ss << fmt::format("${:08x} ", bp.address);
if (bp.is_enabled)
ss << "n";
if (bp.log_on_hit)
ss << "l";
if (bp.break_on_hit)
ss << "b";
if (bp.condition)
ss << "c " << bp.condition->GetText();
bp_strings.emplace_back(ss.str());
}
}

@@ -76,32 +82,43 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bp_strings)
std::istringstream iss(bp_string);
iss.imbue(std::locale::classic());

if (iss.peek() == '$')
iss.ignore();

iss >> std::hex >> bp.address;
iss >> flags;
bp.is_enabled = flags.find('n') != flags.npos;
bp.log_on_hit = flags.find('l') != flags.npos;
bp.break_on_hit = flags.find('b') != flags.npos;
if (flags.find('c') != std::string::npos)
{
iss >> std::ws;
std::string condition;
std::getline(iss, condition);
bp.condition = Expression::TryParse(condition);
}
bp.is_temporary = false;
Add(bp);
Add(std::move(bp));
}
}

void BreakPoints::Add(const TBreakPoint& bp)
void BreakPoints::Add(TBreakPoint bp)
{
if (IsAddressBreakPoint(bp.address))
return;

m_breakpoints.push_back(bp);

JitInterface::InvalidateICache(bp.address, 4, true);

m_breakpoints.emplace_back(std::move(bp));
}

void BreakPoints::Add(u32 address, bool temp)
{
BreakPoints::Add(address, temp, true, false);
BreakPoints::Add(address, temp, true, false, std::nullopt);
}

void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit)
void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
std::optional<Expression> condition)
{
// Only add new addresses
if (IsAddressBreakPoint(address))
@@ -113,8 +130,9 @@ void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit
bp.break_on_hit = break_on_hit;
bp.log_on_hit = log_on_hit;
bp.address = address;
bp.condition = std::move(condition);

m_breakpoints.push_back(bp);
m_breakpoints.emplace_back(std::move(bp));

JitInterface::InvalidateICache(address, 4, true);
}
@@ -4,10 +4,12 @@
#pragma once

#include <cstddef>
#include <optional>
#include <string>
#include <vector>

#include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h"

namespace Common
{
@@ -21,6 +23,7 @@ struct TBreakPoint
bool is_temporary = false;
bool log_on_hit = false;
bool break_on_hit = false;
std::optional<Expression> condition;
};

struct TMemCheck
@@ -59,13 +62,13 @@ class BreakPoints
bool IsAddressBreakPoint(u32 address) const;
bool IsBreakPointEnable(u32 adresss) const;
bool IsTempBreakPoint(u32 address) const;
bool IsBreakPointBreakOnHit(u32 address) const;
bool IsBreakPointLogOnHit(u32 address) const;
const TBreakPoint* GetBreakpoint(u32 address) const;

// Add BreakPoint
void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit);
void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
std::optional<Expression> condition);
void Add(u32 address, bool temp = false);
void Add(const TBreakPoint& bp);
void Add(TBreakPoint bp);

// Modify Breakpoint
bool ToggleBreakPoint(u32 address);
@@ -0,0 +1,269 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Core/PowerPC/Expression.h"

#include <algorithm>
#include <cstdlib>
#include <fmt/format.h>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

#include <expr.h>

#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"

template <typename T>
static T HostRead(u32 address);

template <typename T>
static void HostWrite(T var, u32 address);

template <>
u8 HostRead(u32 address)
{
return PowerPC::HostRead_U8(address);
}

template <>
u16 HostRead(u32 address)
{
return PowerPC::HostRead_U16(address);
}

template <>
u32 HostRead(u32 address)
{
return PowerPC::HostRead_U32(address);
}

template <>
u64 HostRead(u32 address)
{
return PowerPC::HostRead_U64(address);
}

template <>
void HostWrite(u8 var, u32 address)
{
PowerPC::HostWrite_U8(var, address);
}

template <>
void HostWrite(u16 var, u32 address)
{
PowerPC::HostWrite_U16(var, address);
}

template <>
void HostWrite(u32 var, u32 address)
{
PowerPC::HostWrite_U32(var, address);
}

template <>
void HostWrite(u64 var, u32 address)
{
PowerPC::HostWrite_U64(var, address);
}

template <typename T, typename U = T>
static double HostReadFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 1)
return 0;
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 0)));
return Common::BitCast<T>(HostRead<U>(address));
}

template <typename T, typename U = T>
static double HostWriteFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 2)
return 0;
const T var = static_cast<T>(expr_eval(&vec_nth(args, 0)));
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 1)));
HostWrite<U>(Common::BitCast<U>(var), address);
return var;
}

template <typename T, typename U = T>
static double CastFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 1)
return 0;
return Common::BitCast<T>(static_cast<U>(expr_eval(&vec_nth(args, 0))));
}

static std::array<expr_func, 21> g_expr_funcs{{
// For internal storage and comparisons, everything is auto-converted to Double.
// If u64 ints are added, this could produce incorrect results.
{"read_u8", HostReadFunc<u8>},
{"read_s8", HostReadFunc<s8, u8>},
{"read_u16", HostReadFunc<u16>},
{"read_s16", HostReadFunc<s16, u16>},
{"read_u32", HostReadFunc<u32>},
{"read_s32", HostReadFunc<s32, u32>},
{"read_f32", HostReadFunc<float, u32>},
{"read_f64", HostReadFunc<double, u64>},
{"write_u8", HostWriteFunc<u8>},
{"write_u16", HostWriteFunc<u16>},
{"write_u32", HostWriteFunc<u32>},
{"write_f32", HostWriteFunc<float, u32>},
{"write_f64", HostWriteFunc<double, u64>},
{"u8", CastFunc<u8>},
{"s8", CastFunc<s8, u8>},
{"u16", CastFunc<u16>},
{"s16", CastFunc<s16, u16>},
{"u32", CastFunc<u32>},
{"s32", CastFunc<s32, u32>},
{},
}};

void ExprDeleter::operator()(expr* expression) const
{
expr_destroy(expression, nullptr);
}

void ExprVarListDeleter::operator()(expr_var_list* vars) const
{
// Free list elements
expr_destroy(nullptr, vars);
// Free list object
delete vars;
}

Expression::Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars)
: m_text(text), m_expr(std::move(ex)), m_vars(std::move(vars))
{
for (auto* v = m_vars->head; v != nullptr; v = v->next)
{
const std::string_view name = v->name;
VarBinding bind;

if (name.length() >= 2 && name.length() <= 3)
{
if (name[0] == 'r' || name[0] == 'f')
{
char* end = nullptr;
const int index = std::strtol(name.data() + 1, &end, 10);
if (index >= 0 && index <= 31 && end == name.data() + name.length())
{
bind.type = name[0] == 'r' ? VarBindingType::GPR : VarBindingType::FPR;
bind.index = index;
}
}
else if (name == "lr")
{
bind.type = VarBindingType::SPR;
bind.index = SPR_LR;
}
else if (name == "ctr")
{
bind.type = VarBindingType::SPR;
bind.index = SPR_CTR;
}
else if (name == "pc")
{
bind.type = VarBindingType::PCtr;
}
}

m_binds.emplace_back(bind);
}
}

std::optional<Expression> Expression::TryParse(std::string_view text)
{
ExprVarListPointer vars{new expr_var_list{}};
ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), g_expr_funcs.data())};
if (!ex)
return std::nullopt;

return Expression{text, std::move(ex), std::move(vars)};
}

double Expression::Evaluate() const
{
SynchronizeBindings(SynchronizeDirection::From);

double result = expr_eval(m_expr.get());

SynchronizeBindings(SynchronizeDirection::To);

Reporting(result);

return result;
}

void Expression::SynchronizeBindings(SynchronizeDirection dir) const
{
auto bind = m_binds.begin();
for (auto* v = m_vars->head; v != nullptr; v = v->next, ++bind)
{
switch (bind->type)
{
case VarBindingType::Zero:
if (dir == SynchronizeDirection::From)
v->value = 0;
break;
case VarBindingType::GPR:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(GPR(bind->index));
else
GPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
break;
case VarBindingType::FPR:
if (dir == SynchronizeDirection::From)
v->value = rPS(bind->index).PS0AsDouble();
else
rPS(bind->index).SetPS0(v->value);
break;
case VarBindingType::SPR:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(rSPR(bind->index));
else
rSPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
break;
case VarBindingType::PCtr:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(PC);
break;
}
}
}

void Expression::Reporting(const double result) const
{
bool is_nan = std::isnan(result);
std::string message;

for (auto* v = m_vars->head; v != nullptr; v = v->next)
{
if (std::isnan(v->value))
is_nan = true;

fmt::format_to(std::back_inserter(message), " {}={}", v->name, v->value);
}

if (is_nan)
{
message.append("\nBreakpoint condition encountered a NaN");
Core::DisplayMessage("Breakpoint condition has encountered a NaN.", 2000);
}

if (result != 0.0 || is_nan)
NOTICE_LOG_FMT(MEMMAP, "Breakpoint condition returned: {}. Vars:{}", result, message);
}

std::string Expression::GetText() const
{
return m_text;
}
@@ -0,0 +1,74 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

struct expr;
struct expr_var_list;

struct ExprDeleter
{
void operator()(expr* expression) const;
};

using ExprPointer = std::unique_ptr<expr, ExprDeleter>;

struct ExprVarListDeleter
{
void operator()(expr_var_list* vars) const;
};

using ExprVarListPointer = std::unique_ptr<expr_var_list, ExprVarListDeleter>;

class Expression
{
public:
static std::optional<Expression> TryParse(std::string_view text);

double Evaluate() const;

std::string GetText() const;

private:
enum class SynchronizeDirection
{
From,
To,
};

enum class VarBindingType
{
Zero,
GPR,
FPR,
SPR,
PCtr,
};

struct VarBinding
{
VarBindingType type = VarBindingType::Zero;
int index = -1;
};

Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars);

void SynchronizeBindings(SynchronizeDirection dir) const;
void Reporting(const double result) const;

std::string m_text;
ExprPointer m_expr;
ExprVarListPointer m_vars;
std::vector<VarBinding> m_binds;
};

inline bool EvaluateCondition(const std::optional<Expression>& condition)
{
return !condition || condition->Evaluate() != 0.0;
}
@@ -610,16 +610,18 @@ void CheckExternalExceptions()

void CheckBreakPoints()
{
if (!PowerPC::breakpoints.IsBreakPointEnable(PC))
const TBreakPoint* bp = PowerPC::breakpoints.GetBreakpoint(PC);

if (bp == nullptr)
return;

if (PowerPC::breakpoints.IsBreakPointBreakOnHit(PC))
if (bp->break_on_hit)
{
CPU::Break();
if (GDBStub::IsActive())
GDBStub::TakeControl();
}
if (PowerPC::breakpoints.IsBreakPointLogOnHit(PC))
if (bp->log_on_hit)
{
NOTICE_LOG_FMT(MEMMAP,
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "
@@ -405,6 +405,7 @@
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
<ClInclude Include="Core\PowerPC\Expression.h" />
<ClInclude Include="Core\PowerPC\GDBStub.h" />
<ClInclude Include="Core\PowerPC\Gekko.h" />
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
@@ -1029,6 +1030,7 @@
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
<ClCompile Include="Core\PowerPC\Expression.cpp" />
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />
@@ -15,6 +15,7 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/PowerPC/BreakPoints.h"
#include "Core/PowerPC/Expression.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"

@@ -86,7 +87,7 @@ void BreakpointWidget::CreateWidgets()
m_table = new QTableWidget;
m_table->setTabKeyNavigation(false);
m_table->setContentsMargins(0, 0, 0, 0);
m_table->setColumnCount(5);
m_table->setColumnCount(6);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
@@ -160,7 +161,7 @@ void BreakpointWidget::Update()
m_table->clear();

m_table->setHorizontalHeaderLabels(
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")});
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags"), tr("Condition")});

int i = 0;
m_table->setRowCount(i);
@@ -203,6 +204,13 @@ void BreakpointWidget::Update()

m_table->setItem(i, 4, create_item(flags));

QString condition;

if (bp.condition)
condition = QString::fromStdString(bp.condition->GetText());

m_table->setItem(i, 5, create_item(condition));

i++;
}

@@ -387,12 +395,15 @@ void BreakpointWidget::OnContextMenu()

void BreakpointWidget::AddBP(u32 addr)
{
AddBP(addr, false, true, true);
AddBP(addr, false, true, true, {});
}

void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit)
void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit,
const QString& condition)
{
PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit);
PowerPC::breakpoints.Add(
addr, temp, break_on_hit, log_on_hit,
!condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt);

emit BreakpointsChanged();
Update();
@@ -21,7 +21,7 @@ class BreakpointWidget : public QDockWidget
~BreakpointWidget();

void AddBP(u32 addr);
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit);
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition);
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
bool do_break = true);
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
@@ -11,9 +11,11 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QRadioButton>
#include <QVBoxLayout>

#include "Core/PowerPC/Expression.h"
#include "DolphinQt/Debugger/BreakpointWidget.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"

@@ -35,16 +37,25 @@ void NewBreakpointDialog::CreateWidgets()
auto* type_group = new QButtonGroup(this);

// Instruction BP
auto* top_layout = new QHBoxLayout;
m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint"));
m_instruction_bp->setChecked(true);
type_group->addButton(m_instruction_bp);
m_instruction_box = new QGroupBox;
m_instruction_address = new QLineEdit;
m_instruction_condition = new QLineEdit;
m_cond_help_btn = new QPushButton(tr("Help"));

auto* instruction_layout = new QHBoxLayout;
top_layout->addWidget(m_instruction_bp);
top_layout->addStretch();
top_layout->addWidget(m_cond_help_btn);

auto* instruction_layout = new QGridLayout;
m_instruction_box->setLayout(instruction_layout);
instruction_layout->addWidget(new QLabel(tr("Address:")));
instruction_layout->addWidget(m_instruction_address);
instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0);
instruction_layout->addWidget(m_instruction_address, 0, 1);
instruction_layout->addWidget(new QLabel(tr("Condition:")), 1, 0);
instruction_layout->addWidget(m_instruction_condition, 1, 1);

// Memory BP
m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
@@ -102,7 +113,7 @@ void NewBreakpointDialog::CreateWidgets()

auto* layout = new QVBoxLayout;

layout->addWidget(m_instruction_bp);
layout->addLayout(top_layout);
layout->addWidget(m_instruction_box);
layout->addWidget(m_memory_bp);
layout->addWidget(m_memory_box);
@@ -119,6 +130,8 @@ void NewBreakpointDialog::ConnectWidgets()
connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject);

connect(m_cond_help_btn, &QPushButton::clicked, this, &NewBreakpointDialog::ShowConditionHelp);

connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);

@@ -174,7 +187,15 @@ void NewBreakpointDialog::accept()
return;
}

m_parent->AddBP(address, false, do_break, do_log);
const QString condition = m_instruction_condition->text().trimmed();

if (!condition.isEmpty() && !Expression::TryParse(condition.toUtf8().constData()))
{
invalid_input(tr("Condition"));
return;
}

m_parent->AddBP(address, false, do_break, do_log, condition);
}
else
{
@@ -205,3 +226,46 @@ void NewBreakpointDialog::accept()

QDialog::accept();
}

void NewBreakpointDialog::ShowConditionHelp()
{
const auto message = QStringLiteral(
"Set a code breakpoint for when an instruction is executed. Use with the code widget.\n"
"\n"
"Conditions:\n"
"Sets an expression that is evaluated when a breakpoint is hit. If the expression is false "
"or 0, the breakpoint is ignored until hit again. Statements should be separated by a comma. "
"Only the last statement will be used to determine what to do.\n"
"\n"
"Registers that can be referenced:\n"
"GPRs : r0..r31\n"
"FPRs : f0..f31\n LR, CTR, PC\n"
"\n"
"Functions:\n"
"Set a register: r1 = 8\n"
"Casts: s8(0xff). Available: s8, u8, s16, u16, s32, u32\n"
"Read Memory: read_u32(0x80000000). Available: u8, s8, u16, s16, u32, s32, f32, f64\n"
"Write Memory: write_u32(r3, 0x80000000). Available: u8, u16, u32, f32, f64\n"
"*currently writing will always be triggered\n"
"\n"
"Operations:\n"
"Unary: -u, !u, ~u\n"
"Math: * / + -, power: **, remainder: %, shift: <<, >>\n"
"Compare: <, <=, >, >=, ==, !=, &&, ||\n"
"Bitwise: &, |, ^\n"
"\n"
"Examples:\n"
"r4 == 1\n"
"f0 == 1.0 && f2 < 10.0\n"
"r26 <= r0 && ((r5 + 3) & -4) * ((r6 + 3) & -4)* 4 > r0\n"
"p = r3 + 0x8, p == 0x8003510 && read_u32(p) != 0\n"
"Write and break: r4 = 8, 1\n"
"Write and continue: f3 = f1 + f2, 0\n"
"The condition must always be last\n\n"
"All variables will be printed in the Memory Interface log, if there's a hit or a NaN "
"result. To check for issues, assign a variable to your equation, so it can be printed.\n\n"
"Note: All values are internally converted to Doubles for calculations. It's possible for "
"them to go out of range or to become NaN. A warning will be given if NaN is returned, and "
"the var that became NaN will be logged.");
ModalMessageBox::information(this, tr("Conditional help"), message);
}
@@ -29,11 +29,14 @@ class NewBreakpointDialog : public QDialog

void OnBPTypeChanged();
void OnAddressTypeChanged();
void ShowConditionHelp();

// Instruction BPs
QRadioButton* m_instruction_bp;
QGroupBox* m_instruction_box;
QLineEdit* m_instruction_address;
QLineEdit* m_instruction_condition;
QPushButton* m_cond_help_btn;

// Memory BPs
QRadioButton* m_memory_bp;
@@ -12,6 +12,7 @@
<!--For now, header-only libs don't have their own vcxproj/exports, so just supply their include paths to all Dolphin code-->
<AdditionalIncludeDirectories>$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)expr\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>