Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/devkit/data/CapturedMemoryBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,55 @@ bool CapturedMemoryBlock::ContainsMatchingAddress(ra::data::ByteAddress nAddress
return (pAddresses[nIndex >> 3] & nBit);
}

gsl::index CapturedMemoryBlock::GetAddressIndex(ra::data::ByteAddress nAddress) const
{
if (nAddress < m_nFirstAddress)
return -1;

auto nIndex = nAddress - m_nFirstAddress;
if (nIndex >= m_nAddressCount)
return -1;

if (AreAllAddressesMatching())
return gsl::narrow_cast<gsl::index>(nIndex);

const uint8_t* pAddresses = GetMatchingAddressPointer();
Expects(pAddresses != nullptr);
const auto nBit = 1 << (nIndex & 7);
if (!(pAddresses[nIndex >> 3] & nBit))
return -1;

gsl::index nMatchingIndex = 0;

if (nIndex >= 8)
{
const std::array<uint8_t, 16> vBitsSet = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
do
{
const auto nByte = *pAddresses++;
if (nByte != 0)
{
nMatchingIndex += vBitsSet.at(nByte & 0x0F);
nMatchingIndex += vBitsSet.at(nByte >> 4);
}

nIndex -= 8;
} while (nIndex >= 8);
}

uint8_t nMask = 0x01;
const uint8_t nMatch = *pAddresses;
while (nMask < nBit)
{
if (nMatch & nMask)
nMatchingIndex++;

nMask <<= 1;
}

return nMatchingIndex;
}

ra::data::ByteAddress CapturedMemoryBlock::GetMatchingAddress(gsl::index nIndex) const noexcept
{
if (AreAllAddressesMatching())
Expand Down
6 changes: 6 additions & 0 deletions src/devkit/data/CapturedMemoryBlock.hh
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ public:
/// </summary>
bool ContainsMatchingAddress(ByteAddress nAddress) const;

/// <summary>
/// Gets the index of the matching address for the specified address.
/// </summary>
/// <returns>Matching index, <c>-1</c> if not found.</returns>
gsl::index GetAddressIndex(ra::data::ByteAddress nAddress) const;

/// <summary>
/// Specifies which addresses are matching from a slice of an address list.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/services/Search/SearchImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ bool SearchImpl::ContainsAddress(const SearchResults& srResults, ra::data::ByteA
return false;
}

gsl::index SearchImpl::GetAddressIndex(const SearchResults& srResults, ra::data::ByteAddress nAddress) const
{
if (nAddress & (GetStride() - 1))
return -1;

nAddress = ConvertFromRealAddress(nAddress);
const auto nIndex = GetIndexOfBlockForVirtualAddress(srResults, nAddress);
if (nIndex < srResults.m_vBlocks.size())
{
gsl::index nOffset = 0;
for (size_t nScan = 0; nScan < nIndex; ++nScan)
nOffset += srResults.m_vBlocks.at(nScan).GetMatchingAddressCount();

return nOffset + srResults.m_vBlocks.at(nIndex).GetAddressIndex(nAddress);
}

return -1;
}

bool SearchImpl::ExcludeResult(SearchResults& srResults, const SearchResult& pResult) const
{
if (pResult.nAddress & (GetStride() - 1))
Expand Down
2 changes: 2 additions & 0 deletions src/services/Search/SearchImpl.hh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public:
void EnumerateMatches(const SearchResults& srResults,
std::function<bool(const SearchResult&)> fCallback) const;

gsl::index GetAddressIndex(const SearchResults& srResults, ra::data::ByteAddress nAddress) const;

/// <summary>
/// Gets a value from the search results using the provided virtual address (in result.nAddress)
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/services/SearchResults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,14 @@ bool SearchResults::ExcludeResult(const SearchResult& pResult)
return false;
}

gsl::index SearchResults::GetAddressIndex(ra::data::ByteAddress nAddress) const noexcept
{
if (m_pImpl != nullptr)
return m_pImpl->GetAddressIndex(*this, nAddress);

return -1;
}

void SearchResults::EnumerateMatches(std::function<bool(const SearchResult& result)> fCallback) const
{
if (m_pImpl != nullptr)
Expand Down
5 changes: 5 additions & 0 deletions src/services/SearchResults.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ class SearchResults
/// <returns><c>true</c> if result was populated, <c>false</c> if the index was invalid.</returns>
bool GetMatchingAddress(const SearchResult& pSrcResult, _Out_ SearchResult& result) const noexcept;

/// <summary>
/// Gets the index of the specified address. Returns <c>-1</c> if the address was not captured.
/// </summary>
gsl::index GetAddressIndex(ra::data::ByteAddress nAddress) const noexcept;

/// <summary>
/// Gets the raw bytes at the specified address.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions src/ui/viewmodels/MemoryInspectorViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,23 @@ void MemoryInspectorViewModel::OnCurrentAddressChanged(ra::data::ByteAddress nNe

UpdateNoteButtons();

// update selected items in the search results
if (m_pSearch.HasSelection())
{
bool isSelected = false;
for (auto& pResult : m_pSearch.Results())
{
if (pResult.nAddress == nNewAddress)
{
isSelected = pResult.IsSelected();
break;
}
}

if (!isSelected)
m_pSearch.ClearSelection();
}

// update viewer first so memory will be ready for GetValueAtAddress call
m_pViewer.SetAddress(nNewAddress);

Expand Down
17 changes: 17 additions & 0 deletions src/ui/viewmodels/MemorySearchViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,16 @@ void MemorySearchViewModel::PreviousPage()
}
}

void MemorySearchViewModel::ClearSelection()
{
if (m_vSearchResults.empty())
return;

m_vSelectedAddresses.clear();
for (auto& pResult : m_vResults)
pResult.SetSelected(false);
}

void MemorySearchViewModel::SelectRange(gsl::index nFrom, gsl::index nTo, bool bValue)
{
if (m_vSearchResults.empty())
Expand All @@ -1138,6 +1148,7 @@ void MemorySearchViewModel::SelectRange(gsl::index nFrom, gsl::index nTo, bool b
if (!bValue && nFrom == 0 && nTo >= gsl::narrow_cast<gsl::index>(pCurrentResults.MatchingAddressCount()) - 1)
{
m_vSelectedAddresses.clear();
SetValue(HasSelectionProperty, false);
return;
}

Expand Down Expand Up @@ -1180,6 +1191,12 @@ void MemorySearchViewModel::SelectRange(gsl::index nFrom, gsl::index nTo, bool b
}
}

for (auto& vmResult : m_vResults)
{
const bool bIsSelected = m_vSelectedAddresses.find(vmResult.nAddress) != m_vSelectedAddresses.end();
vmResult.SetSelected(bIsSelected);
}

m_vResults.AddNotifyTarget(*this);

SetValue(HasSelectionProperty, (m_vSelectedAddresses.size() > 0));
Expand Down
2 changes: 2 additions & 0 deletions src/ui/viewmodels/MemorySearchViewModel.hh
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ public:
/// </summary>
void ExcludeSelected();
static const BoolModelProperty HasSelectionProperty;
bool HasSelection() const { return GetValue(HasSelectionProperty); }
void ClearSelection();

/// <summary>
/// Bookmarks the currently selected items from the search results.
Expand Down
13 changes: 13 additions & 0 deletions src/ui/win32/bindings/GridBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,19 @@ void GridBinding::OnViewModelIntValueChanged(const IntModelProperty::ChangeArgs&
}
return;
}

if (m_pEnsureVisibleProperty && *m_pEnsureVisibleProperty == args.Property)
{
const auto nIndex = gsl::narrow_cast<gsl::index>(args.tNewValue);
if (nIndex >= 0 && nIndex < gsl::narrow_cast<gsl::index>(GetValue(*m_pScrollMaximumProperty)))
{
InvokeOnUIThread([this, nIndex]() noexcept {
ListView_EnsureVisible(m_hWnd, gsl::narrow_cast<int>(nIndex), FALSE);
});
}

SetValue(*m_pEnsureVisibleProperty, -1);
}
}
else if (m_pEnsureVisibleProperty && *m_pEnsureVisibleProperty == args.Property)
{
Expand Down
19 changes: 19 additions & 0 deletions tests/services/SearchResults_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,22 @@ TEST_CLASS(SearchResults_Tests)
Assert::AreEqual(1U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0x12U, result.nValue);
Assert::AreEqual({ 0 }, results.GetAddressIndex(1U));

Assert::IsTrue(results.GetMatchingAddress(1U, result));
Assert::AreEqual(2U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0x34U, result.nValue);
Assert::AreEqual({ 1 }, results.GetAddressIndex(2U));

Assert::IsTrue(results.GetMatchingAddress(2U, result));
Assert::AreEqual(3U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0xABU, result.nValue);
Assert::AreEqual({ 2 }, results.GetAddressIndex(3U));

Assert::AreEqual({ -1 }, results.GetAddressIndex(0U));
Assert::AreEqual({ -1 }, results.GetAddressIndex(4U));
}

TEST_METHOD(TestInitializeFromMemorySixteenBit)
Expand Down Expand Up @@ -131,11 +137,15 @@ TEST_CLASS(SearchResults_Tests)
Assert::AreEqual(0U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::SixteenBit, result.nSize);
Assert::AreEqual(0x1200U, result.nValue);
Assert::AreEqual({ 0 }, results.GetAddressIndex(0U));

Assert::IsTrue(results.GetMatchingAddress(1U, result));
Assert::AreEqual(2U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::SixteenBit, result.nSize);
Assert::AreEqual(0xAB34U, result.nValue);
Assert::AreEqual({ 1 }, results.GetAddressIndex(2U));

Assert::AreEqual({ -1 }, results.GetAddressIndex(1U));
}

TEST_METHOD(TestInitializeFromMemoryThirtyTwoBitAligned)
Expand Down Expand Up @@ -163,11 +173,15 @@ TEST_CLASS(SearchResults_Tests)
Assert::AreEqual(0U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::ThirtyTwoBit, result.nSize);
Assert::AreEqual(0xAB341200U, result.nValue);
Assert::AreEqual({ 0 }, results.GetAddressIndex(0U));

Assert::IsTrue(results.GetMatchingAddress(1U, result));
Assert::AreEqual(4U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::ThirtyTwoBit, result.nSize);
Assert::AreEqual(0x2044CD56U, result.nValue);
Assert::AreEqual({ 1 }, results.GetAddressIndex(4U));

Assert::AreEqual({ -1 }, results.GetAddressIndex(2U));
}

TEST_METHOD(TestInitializeFromMemorySixteenBitBigEndian)
Expand Down Expand Up @@ -390,6 +404,8 @@ TEST_CLASS(SearchResults_Tests)
Assert::AreEqual(3U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0xABU, result.nValue);

Assert::AreEqual({ 0 }, results2.GetAddressIndex(3U));
}

TEST_METHOD(TestInitializeFromResultsEightBitEqualsInvalidConstant)
Expand Down Expand Up @@ -443,16 +459,19 @@ TEST_CLASS(SearchResults_Tests)
Assert::AreEqual(0U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0x00U, result.nValue);
Assert::AreEqual({ 0 }, results3.GetAddressIndex(0U));

Assert::IsTrue(results3.GetMatchingAddress(1U, result));
Assert::AreEqual(2U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0x00U, result.nValue);
Assert::AreEqual({ 1 }, results3.GetAddressIndex(2U));

Assert::IsTrue(results3.GetMatchingAddress(2U, result));
Assert::AreEqual(5U, result.nAddress);
Assert::AreEqual(ra::data::Memory::Size::EightBit, result.nSize);
Assert::AreEqual(0x00U, result.nValue);
Assert::AreEqual({ 2 }, results3.GetAddressIndex(5U));
}

TEST_METHOD(TestInitializeFromResultsEightBitNotEqualsConstant)
Expand Down
35 changes: 35 additions & 0 deletions tests/ui/viewmodels/MemoryInspectorViewModel_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,41 @@ TEST_CLASS(MemoryInspectorViewModel_Tests)
Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
}

TEST_METHOD(TestSetCurrentAddressKeepsSelectedSearchResult)
{
MemoryInspectorViewModelHarness inspector;
inspector.mockGameContext.SetGameId(1);
inspector.Search().BeginNewSearch();
inspector.Search().ApplyFilter();
Assert::AreEqual({ 9U }, inspector.Search().Results().Count());
inspector.Search().Results().GetItemAt(3)->SetSelected(true);

inspector.SetCurrentAddress({ 3U });

Assert::IsTrue(inspector.Search().Results().GetItemAt(3)->IsSelected());
Assert::IsTrue(inspector.Search().HasSelection());
}

TEST_METHOD(TestSetCurrentAddressClearsSelectedSearchResultForAlternateAddress)
{
MemoryInspectorViewModelHarness inspector;
inspector.mockGameContext.SetGameId(1);
inspector.Search().BeginNewSearch();
inspector.Search().ApplyFilter();
Assert::AreEqual({ 9U }, inspector.Search().Results().Count());
inspector.Search().Results().GetItemAt(3)->SetSelected(true);

inspector.SetCurrentAddress({ 4U });

// Item 3 is no longer selected
Assert::IsFalse(inspector.Search().Results().GetItemAt(3)->IsSelected());

// Item 4 does not get selected
Assert::IsFalse(inspector.Search().Results().GetItemAt(4)->IsSelected());

Assert::IsFalse(inspector.Search().HasSelection());
}

TEST_METHOD(TestSetViewerAddress)
{
MemoryInspectorViewModelHarness inspector;
Expand Down
1 change: 0 additions & 1 deletion tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ TEST_CLASS(MemorySearchViewModel_Tests)
bool CanEditFilterValue() const { return GetValue(CanEditFilterValueProperty); }
bool CanGoToPreviousPage() const { return GetValue(CanGoToPreviousPageProperty); }
bool CanGoToNextPage() const { return GetValue(CanGoToNextPageProperty); }
bool HasSelection() const { return GetValue(HasSelectionProperty); }

void SetScrollOffset(int nValue) { SetValue(ScrollOffsetProperty, nValue); }

Expand Down
Loading