Skip to content

Commit 9bc4e43

Browse files
authored
Support for indirect notes in explain dialog (#1321)
1 parent 130ce10 commit 9bc4e43

14 files changed

Lines changed: 1018 additions & 154 deletions

src/devkit/RADevKit.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<ClCompile Include="data\models\MemoryRegionsModel.cpp" />
7777
<ClCompile Include="data\models\RichPresenceModel.cpp" />
7878
<ClCompile Include="data\models\TriggerValidation.cpp" />
79+
<ClCompile Include="data\util\IndirectNoteResolver.cpp" />
7980
<ClCompile Include="data\Value.cpp" />
8081
<ClCompile Include="services\Http.cpp" />
8182
<ClCompile Include="ui\ImageReference.cpp" />
@@ -111,6 +112,7 @@
111112
<ClInclude Include="data\models\RichPresenceModel.hh" />
112113
<ClInclude Include="data\models\TriggerValidation.hh" />
113114
<ClInclude Include="data\NotifyTargetSet.hh" />
115+
<ClInclude Include="data\util\IndirectNoteResolver.hh" />
114116
<ClInclude Include="data\Value.hh" />
115117
<ClInclude Include="services\Http.hh" />
116118
<ClInclude Include="services\IClock.hh" />

src/devkit/RADevKit.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<Filter Include="ui">
2929
<UniqueIdentifier>{3d6b142b-5f11-4326-a76e-1044b0d23859}</UniqueIdentifier>
3030
</Filter>
31+
<Filter Include="data\util">
32+
<UniqueIdentifier>{8d4e6f6e-e2a2-4798-9c67-691fd63d9d47}</UniqueIdentifier>
33+
</Filter>
3134
</ItemGroup>
3235
<ItemGroup>
3336
<ClInclude Include="services\ServiceLocator.hh">
@@ -201,6 +204,9 @@
201204
<ClInclude Include="data\models\LeaderboardModel.hh">
202205
<Filter>data\models</Filter>
203206
</ClInclude>
207+
<ClInclude Include="data\util\IndirectNoteResolver.hh">
208+
<Filter>data\util</Filter>
209+
</ClInclude>
204210
</ItemGroup>
205211
<ItemGroup>
206212
<ClCompile Include="util\GSL.cpp">
@@ -284,5 +290,8 @@
284290
<ClCompile Include="data\models\LeaderboardModel.cpp">
285291
<Filter>data\models</Filter>
286292
</ClCompile>
293+
<ClCompile Include="data\util\IndirectNoteResolver.cpp">
294+
<Filter>data\util</Filter>
295+
</ClCompile>
287296
</ItemGroup>
288297
</Project>

src/devkit/data/models/MemoryNoteModel.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,10 @@ static bool ParseRange(std::wstring_view svRange, uint32_t& nLow, uint32_t& nHig
12481248
if (nIndex == nStart)
12491249
return false;
12501250

1251-
svHigh = svRange.substr(nStart, nIndex);
1251+
if (nIndex < svRange.size() && ra::util::String::IsAlpha(svRange.at(nIndex)))
1252+
return false;
1253+
1254+
svHigh = svRange.substr(nStart, nIndex - nStart);
12521255
}
12531256

12541257
nLow = Convert(svLow, isHex);
@@ -1292,7 +1295,7 @@ static std::wstring_view GetValues(const std::wstring_view svLine)
12921295

12931296
// no right bracket found. take the rest of the line
12941297
if (nRightBracket == std::wstring::npos)
1295-
nRightBracket = svLine.size();
1298+
return {};
12961299

12971300
return svLine.substr(nLeftBracket + 1, nRightBracket - nLeftBracket - 1);
12981301
}
@@ -1562,6 +1565,9 @@ std::wstring MemoryNoteModel::GetSummary() const
15621565
}
15631566
}
15641567

1568+
while (!svNote.empty() && ra::util::String::IsSpace(svNote.back()))
1569+
svNote = svNote.substr(0, svNote.size() - 1);
1570+
15651571
return TrimSize(std::wstring(svNote), false);
15661572
}
15671573

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#include "IndirectNoteResolver.hh"
2+
3+
#include "context/IEmulatorMemoryContext.hh"
4+
5+
#include "services/ServiceLocator.hh"
6+
7+
#include "util/Strings.hh"
8+
9+
#include <rcheevos/src/rcheevos/rc_internal.h>
10+
11+
namespace ra {
12+
namespace data {
13+
namespace util {
14+
15+
static uint32_t ResolveOperandRecursive(const rc_operand_t* pOperand,
16+
std::vector<ra::data::util::IndirectNoteResolver::Node>& vParentChain,
17+
const ra::data::models::MemoryNotesModel& pMemoryNotes)
18+
{
19+
rc_typed_value_t pValue;
20+
rc_evaluate_operand(&pValue, pOperand, nullptr);
21+
rc_typed_value_convert(&pValue, RC_VALUE_TYPE_UNSIGNED);
22+
23+
// check for {recall}
24+
if (pOperand->type == RC_OPERAND_RECALL)
25+
{
26+
auto* pParentNote = !vParentChain.empty() ? vParentChain.back().pNote : nullptr;
27+
28+
auto& pNode = vParentChain.emplace_back();
29+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::Recall;
30+
31+
if (pParentNote)
32+
pNode.pNote = pParentNote->GetPointerNoteAtOffset(pValue.value.u32);
33+
else
34+
pNode.pNote = pMemoryNotes.FindMemoryNoteModel(pValue.value.u32, false);
35+
36+
pNode.nValue = pValue.value.u32;
37+
return pValue.value.u32;
38+
}
39+
40+
// check for constant offset
41+
if (!rc_operand_is_memref(pOperand))
42+
{
43+
auto* pParentNote = !vParentChain.empty() ? vParentChain.back().pNote : nullptr;
44+
45+
auto& pNode = vParentChain.emplace_back();
46+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::ConstantOffset;
47+
48+
if (pParentNote)
49+
pNode.pNote = pParentNote->GetPointerNoteAtOffset(pValue.value.u32);
50+
51+
pNode.nValue = pValue.value.u32;
52+
return pValue.value.u32;
53+
}
54+
55+
// check for root pointer
56+
if (pOperand->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF)
57+
{
58+
auto& pNode = vParentChain.emplace_back();
59+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::Address;
60+
pNode.nValue = pOperand->value.memref->address;
61+
62+
// find the memory note associated to the root pointer
63+
pNode.pNote = pMemoryNotes.FindMemoryNoteModel(pNode.nValue, false);
64+
if (!pNode.pNote)
65+
{
66+
const auto nStartAddress = pMemoryNotes.FindNoteStart(pNode.nValue);
67+
if (nStartAddress != 0xFFFFFFFF && nStartAddress != pNode.nValue)
68+
{
69+
const auto nOffset = pNode.nValue - nStartAddress;
70+
pNode.nValue = nStartAddress;
71+
const auto* pNote = pMemoryNotes.FindMemoryNoteModel(nStartAddress);
72+
pNode.pNote = pNote;
73+
74+
auto& pNode2 = vParentChain.emplace_back();
75+
pNode2.nType = ra::data::util::IndirectNoteResolver::NodeType::Constant;
76+
pNode2.nValue = nOffset;
77+
pNode2.nModifierType = RC_OPERATOR_ADD;
78+
pNode2.pNote = pNote;
79+
}
80+
}
81+
82+
return pValue.value.u32;
83+
}
84+
85+
// process offset and recurse
86+
GSL_SUPPRESS_TYPE1 const auto* pModifiedMemref =
87+
reinterpret_cast<const rc_modified_memref_t*>(pOperand->value.memref);
88+
ResolveOperandRecursive(&pModifiedMemref->parent, vParentChain, pMemoryNotes);
89+
90+
if (vParentChain.back().nType == ra::data::util::IndirectNoteResolver::NodeType::Address)
91+
vParentChain.back().nType = ra::data::util::IndirectNoteResolver::NodeType::DereferencedAddress;
92+
93+
// calculate current values of both operands
94+
rc_evaluate_operand(&pValue, &pModifiedMemref->parent, nullptr);
95+
rc_typed_value_convert(&pValue, RC_VALUE_TYPE_UNSIGNED);
96+
97+
rc_typed_value_t pModifier{};
98+
rc_evaluate_operand(&pModifier, &pModifiedMemref->modifier, nullptr);
99+
rc_typed_value_convert(&pModifier, RC_VALUE_TYPE_UNSIGNED);
100+
101+
// if it's an indirect read, determine if it's a pointer or index offset
102+
if (pModifiedMemref->modifier_type == RC_OPERATOR_INDIRECT_READ)
103+
{
104+
// value is the parent pointer, modifier is the offset. combine them to get the new address
105+
rc_typed_value_combine(&pValue, &pModifier, RC_OPERATOR_ADD);
106+
const auto nAddress = pModifier.value.u32;
107+
108+
const auto* pParentNote = !vParentChain.empty() ? vParentChain.back().pNote : nullptr;
109+
if (pParentNote && !pParentNote->IsPointer())
110+
{
111+
// if the parent note is not a pointer, assume it's an index
112+
Expects(pModifiedMemref->modifier.type == RC_OPERAND_CONST);
113+
auto& pNode = vParentChain.emplace_back();
114+
pNode.nValue = nAddress;
115+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::ArrayOffset;
116+
117+
// find the memory note associated to the start of the array
118+
pNode.pNote = pMemoryNotes.FindMemoryNoteModel(nAddress, false);
119+
120+
// return the address offset into the array
121+
return pValue.value.u32;
122+
}
123+
124+
// process the pointer
125+
ResolveOperandRecursive(&pModifiedMemref->modifier, vParentChain, pMemoryNotes);
126+
vParentChain.back().nModifierType = RC_OPERATOR_INDIRECT_READ;
127+
128+
return pValue.value.u32;
129+
}
130+
131+
// not an indirect read. must be a scalar.
132+
133+
// don't report mask on every pointer evaluation
134+
if (pModifiedMemref->modifier_type != RC_OPERATOR_AND)
135+
{
136+
const auto* pParentNote = !vParentChain.empty() ? vParentChain.back().pNote : nullptr;
137+
138+
auto& pNode = vParentChain.emplace_back();
139+
pNode.nModifierType = pModifiedMemref->modifier_type;
140+
pNode.pNote = pParentNote;
141+
142+
if (pModifiedMemref->modifier.type == RC_OPERAND_RECALL)
143+
{
144+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::Recall;
145+
pNode.nValue = pModifier.value.u32;
146+
}
147+
else if (rc_operand_is_memref(&pModifiedMemref->modifier))
148+
{
149+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::Address;
150+
pNode.nValue = pModifiedMemref->modifier.value.memref->address;
151+
}
152+
else
153+
{
154+
pNode.nType = ra::data::util::IndirectNoteResolver::NodeType::Constant;
155+
pNode.nValue = pModifiedMemref->modifier.value.num;
156+
}
157+
}
158+
159+
// return the result of combining the value and the modifier
160+
rc_typed_value_combine(&pValue, &pModifier, pModifiedMemref->modifier_type);
161+
return pValue.value.u32;
162+
}
163+
164+
ra::data::ByteAddress IndirectNoteResolver::ResolveOperand(
165+
const struct rc_condition_t& pCondition, bool bLeafIsOperand1,
166+
std::vector<Node>& vParentChain) const
167+
{
168+
Expects(m_pMemoryNotes != nullptr);
169+
170+
if (bLeafIsOperand1)
171+
{
172+
const auto* pOperand1 = rc_condition_get_real_operand1(&pCondition);
173+
if (rc_operand_is_memref(pOperand1))
174+
return ResolveOperandRecursive(pOperand1, vParentChain, *m_pMemoryNotes);
175+
}
176+
else
177+
{
178+
if (rc_operand_is_memref(&pCondition.operand2))
179+
return ResolveOperandRecursive(&pCondition.operand2, vParentChain, *m_pMemoryNotes);
180+
}
181+
182+
return 0;
183+
}
184+
185+
std::wstring IndirectNoteResolver::BuildPath(const std::vector<Node>& vParentChain) const
186+
{
187+
const auto& pMemoryContext = ra::services::ServiceLocator::Get<ra::context::IEmulatorMemoryContext>();
188+
std::wstring sPointerChain;
189+
190+
for (const auto& pNode : vParentChain)
191+
{
192+
switch (pNode.nModifierType)
193+
{
194+
case RC_OPERATOR_INDIRECT_READ:
195+
case RC_OPERATOR_ADD:
196+
sPointerChain.push_back(L'+');
197+
break;
198+
case RC_OPERATOR_SUB:
199+
sPointerChain.push_back(L'-');
200+
break;
201+
case RC_OPERATOR_MULT:
202+
sPointerChain.push_back(L'*');
203+
break;
204+
case RC_OPERATOR_DIV:
205+
sPointerChain.push_back(L'/');
206+
break;
207+
case RC_OPERATOR_AND:
208+
sPointerChain.push_back(L'&');
209+
break;
210+
case RC_OPERATOR_XOR:
211+
sPointerChain.push_back(L'^');
212+
break;
213+
case RC_OPERATOR_MOD:
214+
sPointerChain.push_back(L'%');
215+
break;
216+
default:
217+
break;
218+
}
219+
220+
switch (pNode.nType)
221+
{
222+
case ra::data::util::IndirectNoteResolver::NodeType::Recall:
223+
sPointerChain += ra::util::String::Printf(L"{recall:0x%02x}", pNode.nValue);
224+
break;
225+
226+
case ra::data::util::IndirectNoteResolver::NodeType::ConstantOffset:
227+
sPointerChain += ra::util::String::Printf(L"0x%02x", pNode.nValue);
228+
break;
229+
230+
case ra::data::util::IndirectNoteResolver::NodeType::Address:
231+
sPointerChain += pMemoryContext.FormatAddress(pNode.nValue);
232+
break;
233+
234+
case ra::data::util::IndirectNoteResolver::NodeType::DereferencedAddress:
235+
sPointerChain.push_back('$');
236+
sPointerChain += pMemoryContext.FormatAddress(pNode.nValue);
237+
break;
238+
239+
case ra::data::util::IndirectNoteResolver::NodeType::ArrayOffset:
240+
sPointerChain.insert(0, ra::util::String::Printf(L"%s[", pMemoryContext.FormatAddress(pNode.nValue)));
241+
sPointerChain.push_back(']');
242+
break;
243+
244+
case ra::data::util::IndirectNoteResolver::NodeType::Constant:
245+
switch (pNode.nModifierType)
246+
{
247+
case RC_OPERATOR_AND:
248+
case RC_OPERATOR_XOR: // use hex for bitwise combines
249+
sPointerChain += ra::util::String::Printf(L"0x%02x", pNode.nValue);
250+
break;
251+
252+
default:
253+
if (pNode.nValue >= 0x1000 && (pNode.nValue & 0xFF) == 0) // large multiple of 256, use hex
254+
sPointerChain += ra::util::String::Printf(L"0x%04x", pNode.nValue);
255+
else if (pNode.nValue >= 10)
256+
sPointerChain += ra::util::String::Printf(L"0x%02x", pNode.nValue);
257+
else // otherwise use decimal
258+
sPointerChain += std::to_wstring(pNode.nValue);
259+
break;
260+
}
261+
break;
262+
263+
}
264+
}
265+
266+
return sPointerChain;
267+
}
268+
269+
270+
} // namespace util
271+
} // namespace data
272+
} // namespace ra

0 commit comments

Comments
 (0)