Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multi-level indirect code notes in address tooltips #1129

Merged
merged 10 commits into from
Nov 24, 2024
166 changes: 141 additions & 25 deletions src/data/models/CodeNoteModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ struct CodeNoteModel::PointerData
uint32_t RawPointerValue = 0xFFFFFFFF; // last raw value of pointer captured
ra::ByteAddress PointerAddress = 0xFFFFFFFF; // raw pointer value converted to RA address
unsigned int OffsetRange = 0; // highest offset captured within pointer block
unsigned int HeaderLength = 0; // length of note text not associated to OffsetNotes
bool HasPointers = false; // true if there are nested pointers

enum OffsetType
{
Expand Down Expand Up @@ -55,6 +57,11 @@ CodeNoteModel& CodeNoteModel::operator=(CodeNoteModel&& pOther) noexcept
return *this;
}

std::wstring CodeNoteModel::GetPointerDescription() const
{
return m_pPointerData != nullptr ? m_sNote.substr(0, m_pPointerData->HeaderLength) : std::wstring();
}

ra::ByteAddress CodeNoteModel::GetPointerAddress() const noexcept
{
return m_pPointerData != nullptr ? m_pPointerData->PointerAddress : 0xFFFFFFFF;
Expand All @@ -75,24 +82,46 @@ static ra::ByteAddress ConvertPointer(ra::ByteAddress nAddress)
return nAddress;
}

bool CodeNoteModel::SetRawPointerValue(uint32_t nValue)
void CodeNoteModel::UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::data::context::EmulatorContext& pEmulatorContext,
NoteMovedFunction fNoteMovedCallback)
{
if (m_pPointerData == nullptr)
return false;
return;

if (nValue == m_pPointerData->RawPointerValue)
return false;
m_pPointerData->RawPointerValue = nValue;
const uint32_t nValue = pEmulatorContext.ReadMemory(nAddress, GetMemSize());
if (nValue != m_pPointerData->RawPointerValue)
{
m_pPointerData->RawPointerValue = nValue;

const auto nNewAddress = (m_pPointerData->OffsetType == PointerData::OffsetType::Converted)
? ConvertPointer(nValue) : nValue;
const auto nNewAddress = (m_pPointerData->OffsetType == PointerData::OffsetType::Converted)
? ConvertPointer(nValue) : nValue;

const auto nOldAddress = m_pPointerData->PointerAddress;
if (nNewAddress == nOldAddress)
return false;
const auto nOldAddress = m_pPointerData->PointerAddress;
if (nNewAddress != nOldAddress)
{
m_pPointerData->PointerAddress = nNewAddress;
if (fNoteMovedCallback)
{
for (const auto& pNote : m_pPointerData->OffsetNotes)
{
if (!pNote.CodeNote.IsPointer())
fNoteMovedCallback(nOldAddress + pNote.Offset, nNewAddress + pNote.Offset, pNote.CodeNote);
}
}
}
}

m_pPointerData->PointerAddress = nNewAddress;
return true;
if (m_pPointerData->HasPointers)
{
for (auto& pNote : m_pPointerData->OffsetNotes)
{
if (pNote.CodeNote.IsPointer())
{
pNote.CodeNote.UpdateRawPointerValue(m_pPointerData->PointerAddress + pNote.Offset,
pEmulatorContext, fNoteMovedCallback);
}
}
}
}

const CodeNoteModel* CodeNoteModel::GetPointerNoteAtOffset(int nOffset) const
Expand Down Expand Up @@ -129,10 +158,16 @@ std::pair<ra::ByteAddress, const CodeNoteModel*> CodeNoteModel::GetPointerNoteAt
return {0, nullptr};

const auto nPointerAddress = m_pPointerData->PointerAddress;
const auto nConvertedAddress = (m_pPointerData->OffsetType == PointerData::OffsetType::Overflow)
? ConvertPointer(nPointerAddress) : nPointerAddress;

if (nAddress >= nConvertedAddress && nAddress < nConvertedAddress + m_pPointerData->OffsetRange)
bool bAddressValid = true;
if (m_pPointerData->OffsetType == PointerData::OffsetType::Converted)
{
const auto nConvertedAddress = ConvertPointer(nPointerAddress);
bAddressValid = nAddress >= nConvertedAddress && nAddress < nConvertedAddress + m_pPointerData->OffsetRange;
}

// if address is in the struct, look for a matching field
if (bAddressValid)
{
auto nOffset = ra::to_signed(nAddress - nPointerAddress);

Expand All @@ -155,6 +190,21 @@ std::pair<ra::ByteAddress, const CodeNoteModel*> CodeNoteModel::GetPointerNoteAt
}
}

// check pointer chains
if (m_pPointerData->HasPointers)
{
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
if (pOffsetNote.CodeNote.IsPointer())
{
auto pNestedObject = pOffsetNote.CodeNote.GetPointerNoteAtAddress(nAddress);
if (pNestedObject.second)
return pNestedObject;
}
}
}

// not found
return {0, nullptr};
}

Expand Down Expand Up @@ -462,18 +512,83 @@ void CodeNoteModel::ExtractSize(const std::wstring& sNote)
}
}

static void RemoveIndentPrefix(std::wstring& sNote)
{
auto nLineIndex = sNote.find('\n');
if (nLineIndex == std::wstring::npos)
return;

for (size_t nIndent = nLineIndex + 1; nIndent + 1 < sNote.length(); ++nIndent)
{
auto c = sNote.at(nIndent);
if (c != '+')
{
if (c == '\n')
nLineIndex = nIndent;

continue;
}

c = sNote.at(nIndent + 1);
if (isdigit(c)) // found +N
{
if (nIndent > nLineIndex + 1)
{
const auto sPrefix = sNote.substr(nLineIndex, nIndent - nLineIndex); // capture "\n" + prefix
auto nIndex = nLineIndex;
do
{
sNote.erase(nIndex + 1, sPrefix.length() - 1);
nIndex = sNote.find(sPrefix, nIndex + 1);
} while (nIndex != std::wstring::npos);
}
break;
}
}
}

void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nIndex)
{
auto pointerData = std::make_unique<PointerData>();
pointerData->HeaderLength = gsl::narrow_cast<unsigned int>(nIndex);
nIndex += 2;

auto pointerData = std::make_unique<PointerData>();
do
{
PointerData::OffsetCodeNote offsetNote;
const auto nNextIndex = sNote.find(L"\n+", nIndex);
auto sNextNote = sNote.substr(nIndex, nNextIndex - nIndex);
ra::Trim(sNextNote);
offsetNote.CodeNote.SetAuthor(m_sAuthor);

// the next note starts when we find a '+' at the start of a line.
auto nNextIndex = sNote.find(L"\n+", nIndex);
auto nStopIndex = nNextIndex;

if (nNextIndex != std::wstring::npos)
{
// a chain of plusses indicates an indented nested note. include them
//
// [32-bit pointer] global data
// +0x20 [32-bit pointer] user data
// ++0x08 [16-bit] points
//
while (nNextIndex + 2 < sNote.length() && !isdigit(sNote.at(nNextIndex + 2)))
{
nNextIndex = nStopIndex = sNote.find(L"\n+", nNextIndex + 2);
if (nNextIndex == std::wstring::npos)
break;
}

// remove trailing whitespace
if (nStopIndex != std::wstring::npos)
{
while (nStopIndex > 0 && isspace(sNote.at(nStopIndex - 1)))
nStopIndex--;
}
}

auto sNextNote = sNote.substr(nIndex, nStopIndex - nIndex);
RemoveIndentPrefix(sNextNote);

// extract the offset
wchar_t* pEnd = nullptr;

try
Expand All @@ -491,6 +606,7 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
if (!pEnd || isalnum(*pEnd))
return;

// skip over [whitespace] [optional separator] [whitespace]
const wchar_t* pStop = sNextNote.c_str() + sNextNote.length();
while (pEnd < pStop && isspace(*pEnd))
pEnd++;
Expand All @@ -501,8 +617,8 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
pEnd++;
}

offsetNote.CodeNote.SetAuthor(m_sAuthor);
offsetNote.CodeNote.SetNote(sNextNote.substr(pEnd - sNextNote.c_str()));
pointerData->HasPointers |= offsetNote.CodeNote.IsPointer();

const auto nRangeOffset = offsetNote.Offset + offsetNote.CodeNote.GetBytes();
pointerData->OffsetRange = std::max(pointerData->OffsetRange, nRangeOffset);
Expand Down Expand Up @@ -540,26 +656,26 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
}

void CodeNoteModel::EnumeratePointerNotes(
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> callback) const
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> fCallback) const
{
if (m_pPointerData == nullptr)
return;

if (m_pPointerData->OffsetType == PointerData::OffsetType::Overflow)
EnumeratePointerNotes(m_pPointerData->RawPointerValue, callback);
EnumeratePointerNotes(m_pPointerData->RawPointerValue, fCallback);
else
EnumeratePointerNotes(m_pPointerData->PointerAddress, callback);
EnumeratePointerNotes(m_pPointerData->PointerAddress, fCallback);
}

void CodeNoteModel::EnumeratePointerNotes(ra::ByteAddress nPointerAddress,
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> callback) const
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> fCallback) const
{
if (m_pPointerData == nullptr)
return;

for (const auto& pNote : m_pPointerData->OffsetNotes)
{
if (!callback(nPointerAddress + pNote.Offset, pNote.CodeNote))
if (!fCallback(nPointerAddress + pNote.Offset, pNote.CodeNote))
break;
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/data/models/CodeNoteModel.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "data/Types.hh"

#include "data/context/EmulatorContext.hh"

namespace ra {
namespace data {
namespace models {
Expand All @@ -27,18 +29,21 @@ public:
void SetNote(const std::wstring& sNote);

bool IsPointer() const noexcept { return m_pPointerData != nullptr; }
std::wstring GetPointerDescription() const;
ra::ByteAddress GetPointerAddress() const noexcept;
uint32_t GetRawPointerValue() const noexcept;
bool SetRawPointerValue(uint32_t nValue);
const CodeNoteModel* GetPointerNoteAtOffset(int nOffset) const;
std::pair<ra::ByteAddress, const CodeNoteModel*> GetPointerNoteAtAddress(ra::ByteAddress nAddress) const;

typedef std::function<void(ra::ByteAddress nOldAddress, ra::ByteAddress nNewAddress, const CodeNoteModel&)> NoteMovedFunction;
void UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::data::context::EmulatorContext& pEmulatorContext, NoteMovedFunction fNoteMovedCallback);

bool GetPreviousAddress(ra::ByteAddress nBeforeAddress, ra::ByteAddress& nPreviousAddress) const;
bool GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddress& nNextAddress) const;

void EnumeratePointerNotes(ra::ByteAddress nPointerAddress,
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> callback) const;
void EnumeratePointerNotes(std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> callback) const;
std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> fCallback) const;
void EnumeratePointerNotes(std::function<bool(ra::ByteAddress nAddress, const CodeNoteModel&)> fCallback) const;

private:
std::string m_sAuthor;
Expand Down
50 changes: 7 additions & 43 deletions src/data/models/CodeNotesModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void CodeNotesModel::AddCodeNote(ra::ByteAddress nAddress, const std::string& sA

// capture the initial value of the pointer
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
note.SetRawPointerValue(pEmulatorContext.ReadMemory(nAddress, note.GetMemSize()));
note.UpdateRawPointerValue(nAddress, pEmulatorContext, nullptr);
}

{
Expand Down Expand Up @@ -340,7 +340,7 @@ void CodeNotesModel::SetCodeNote(ra::ByteAddress nAddress, const std::wstring& s
}
}

const CodeNoteModel* CodeNotesModel::FindCodeNoteInternal(ra::ByteAddress nAddress) const
const CodeNoteModel* CodeNotesModel::FindCodeNoteModel(ra::ByteAddress nAddress) const
{
const auto pIter = m_mCodeNotes.find(nAddress);
if (pIter != m_mCodeNotes.end())
Expand All @@ -365,29 +365,6 @@ std::pair<ra::ByteAddress, const CodeNoteModel*>
return {0, nullptr};
}

const std::wstring* CodeNotesModel::FindIndirectCodeNote(ra::ByteAddress nAddress, unsigned nOffset) const
{
if (!m_bHasPointers)
return nullptr;

for (const auto& pCodeNote : m_mCodeNotes)
{
if (!pCodeNote.second.IsPointer())
continue;

if (nAddress == pCodeNote.first)
{
const auto* pOffsetNote = pCodeNote.second.GetPointerNoteAtOffset(nOffset);
if (pOffsetNote != nullptr)
return &pOffsetNote->GetNote();

break;
}
}

return nullptr;
}

ra::ByteAddress CodeNotesModel::GetIndirectSource(ra::ByteAddress nAddress) const
{
if (m_bHasPointers)
Expand Down Expand Up @@ -505,25 +482,12 @@ void CodeNotesModel::DoFrame()

for (auto& pNote : m_mCodeNotes)
{
if (!pNote.second.IsPointer())
continue;

const auto nOldAddress = pNote.second.GetPointerAddress();
const auto nNewRawAddress = pEmulatorContext.ReadMemory(pNote.first, pNote.second.GetMemSize());
if (pNote.second.SetRawPointerValue(nNewRawAddress))
if (pNote.second.IsPointer())
{
pNote.second.EnumeratePointerNotes(nOldAddress,
[this](ra::ByteAddress nAddress, const CodeNoteModel&)
{
m_fCodeNoteChanged(nAddress, L"");
return true;
});

pNote.second.EnumeratePointerNotes(
[this](ra::ByteAddress nAddress, const CodeNoteModel& pOffsetNote)
{
m_fCodeNoteChanged(nAddress, pOffsetNote.GetNote());
return true;
pNote.second.UpdateRawPointerValue(pNote.first, pEmulatorContext,
[this](ra::ByteAddress nOldAddress, ra::ByteAddress nNewAddress, const CodeNoteModel& pOffsetNote) {
m_fCodeNoteChanged(nOldAddress, L"");
m_fCodeNoteChanged(nNewAddress, pOffsetNote.GetNote());
});
}
}
Expand Down
Loading
Loading