Skip to content

Commit

Permalink
upgrade rcheevos (#1128)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Nov 14, 2024
1 parent 77b1cae commit 8a26afb
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 136 deletions.
2 changes: 1 addition & 1 deletion rcheevos
Submodule rcheevos updated 47 files
+11 −0 CHANGELOG.md
+4 −0 include/rc_api_runtime.h
+40 −29 include/rc_runtime_types.h
+17 −0 src/rapi/rc_api_runtime.c
+174 −41 src/rc_client.c
+21 −0 src/rc_client_raintegration.c
+394 −0 src/rc_client_types.natvis
+103 −16 src/rc_compat.c
+27 −3 src/rc_compat.h
+36 −0 src/rc_libretro.c
+1 −1 src/rc_version.h
+8 −4 src/rcheevos/alloc.c
+238 −132 src/rcheevos/condition.c
+603 −322 src/rcheevos/condset.c
+65 −12 src/rcheevos/consoleinfo.c
+7 −7 src/rcheevos/lboard.c
+100 −39 src/rcheevos/memref.c
+192 −34 src/rcheevos/operand.c
+116 −18 src/rcheevos/rc_internal.h
+389 −0 src/rcheevos/rc_runtime_types.natvis
+59 −89 src/rcheevos/rc_validate.c
+1 −1 src/rcheevos/richpresence.c
+29 −3 src/rcheevos/runtime.c
+85 −7 src/rcheevos/runtime_progress.c
+28 −11 src/rcheevos/trigger.c
+242 −160 src/rcheevos/value.c
+24 −3 src/rhash/hash.c
+1 −0 test/Makefile
+42 −0 test/rapi/test_rc_api_runtime.c
+5 −0 test/rcheevos-test.vcxproj
+11 −0 test/rcheevos-test.vcxproj.filters
+7 −7 test/rcheevos/test_condition.c
+331 −1 test/rcheevos/test_condset.c
+4 −3 test/rcheevos/test_consoleinfo.c
+38 −0 test/rcheevos/test_lboard.c
+98 −28 test/rcheevos/test_memref.c
+7 −7 test/rcheevos/test_operand.c
+7 −2 test/rcheevos/test_rc_validate.c
+42 −0 test/rcheevos/test_richpresence.c
+54 −0 test/rcheevos/test_runtime_progress.c
+43 −6 test/rcheevos/test_trigger.c
+11 −6 test/rcheevos/test_value.c
+4 −0 test/rhash/test_hash.c
+2 −0 test/test.c
+31 −22 test/test_rc_client.c
+26 −1 test/test_rc_libretro.c
+9 −0 test/test_types.natvis
3 changes: 3 additions & 0 deletions src/rcheevos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@
</ClCompile>
<ClCompile Include="..\rcheevos\src\rhash\md5.c" />
</ItemGroup>
<ItemGroup>
<Natvis Include="..\rcheevos\src\rcheevos\rc_runtime_types.natvis" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/rcheevos.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,9 @@
<ClInclude Include="..\rcheevos\include\rc_client_raintegration.h" />
<ClInclude Include="..\rcheevos\src\rc_client_external.h" />
</ItemGroup>
<ItemGroup>
<Natvis Include="..\rcheevos\src\rcheevos\rc_runtime_types.natvis">
<Filter>rcheevos</Filter>
</Natvis>
</ItemGroup>
</Project>
190 changes: 70 additions & 120 deletions src/ui/viewmodels/TriggerConditionViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,21 @@ void TriggerConditionViewModel::ChangeOperandType(const StringModelProperty& sVa
void TriggerConditionViewModel::SetOperand(const IntModelProperty& pTypeProperty,
const IntModelProperty& pSizeProperty, const StringModelProperty& pValueProperty, const rc_operand_t& operand)
{
if (rc_operand_is_memref(&operand) && operand.value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF)
{
GSL_SUPPRESS_TYPE1 const auto* pModifiedMemref = reinterpret_cast<rc_modified_memref_t*>(operand.value.memref);
if (pModifiedMemref->modifier_type != RC_OPERATOR_INDIRECT_READ)
{
// if the modified memref is not an indirect read, the size and address are stored in the modifier.
SetOperand(pTypeProperty, pSizeProperty, pValueProperty, pModifiedMemref->modifier);
return;
}

// if the modified memref is an indirect read, the modifier is just a constant with the offset.
// the size is local to the operand, and the offset is also stored in the local address field.
// just proceed normally.
}

rc_typed_value_t pValue{};

const auto nType = static_cast<TriggerOperandType>(operand.type);
Expand Down Expand Up @@ -681,9 +696,48 @@ std::wstring TriggerConditionViewModel::GetValueTooltip(unsigned int nValue)
return std::to_wstring(nValue);
}

static bool IsIndirectMemref(const rc_operand_t& operand) noexcept
static ra::ByteAddress GetParentAddress(const rc_modified_memref_t* pModifiedMemref)
{
return rc_operand_is_memref(&operand) && operand.value.memref->value.is_indirect;
switch (pModifiedMemref->parent.value.memref->value.memref_type)
{
default:
return pModifiedMemref->parent.value.memref->address;

case RC_MEMREF_TYPE_MODIFIED_MEMREF:
GSL_SUPPRESS_TYPE1 const auto* pModifiedParentMemref =
reinterpret_cast<const rc_modified_memref_t*>(pModifiedMemref->parent.value.memref);

// chained pointer
if (pModifiedParentMemref->modifier_type == RC_OPERATOR_INDIRECT_READ)
return NESTED_POINTER_ADDRESS;

// indirect addresses have to be on the left
if (rc_operand_is_memref(&pModifiedParentMemref->parent))
return GetParentAddress(pModifiedParentMemref);

return UNKNOWN_ADDRESS;
}
}

static ra::ByteAddress GetIndirectAddressFromOperand(const rc_operand_t* pOperand, ra::ByteAddress nAddress, ra::ByteAddress* pPointerAddress)
{
if (pOperand->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF)
return nAddress;

GSL_SUPPRESS_TYPE1 const auto* pModifiedMemref = reinterpret_cast<const rc_modified_memref_t*>(pOperand->value.memref);
if (pModifiedMemref->modifier_type != RC_OPERATOR_INDIRECT_READ)
return nAddress;

if (pPointerAddress)
*pPointerAddress = GetParentAddress(pModifiedMemref);

rc_typed_value_t value{}, offset;
offset.type = RC_VALUE_TYPE_UNSIGNED;
offset.value.u32 = nAddress;
rc_evaluate_operand(&value, &pModifiedMemref->parent, nullptr);
rc_typed_value_add(&value, &offset);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
return value.value.u32;
}

ra::ByteAddress TriggerConditionViewModel::GetIndirectAddress(ra::ByteAddress nAddress,
Expand All @@ -704,7 +758,6 @@ ra::ByteAddress TriggerConditionViewModel::GetIndirectAddress(ra::ByteAddress nA
rc_condition_t* pFirstCondition = nullptr;

const auto* pTrigger = pTriggerViewModel->GetTriggerFromString();
bool bProcessPause = false;
if (pTrigger != nullptr)
{
// if the trigger is managed by the viewmodel (not the runtime) then we need to update the memrefs
Expand All @@ -714,7 +767,6 @@ ra::ByteAddress TriggerConditionViewModel::GetIndirectAddress(ra::ByteAddress nA
if (nIndex == 0)
{
pFirstCondition = pTrigger->requirement->conditions;
bProcessPause = pTrigger->requirement->has_pause;
}
else
{
Expand All @@ -726,134 +778,32 @@ ra::ByteAddress TriggerConditionViewModel::GetIndirectAddress(ra::ByteAddress nA
return nAddress;

pFirstCondition = pAlt->conditions;
bProcessPause = pAlt->has_pause;
}
}
else
{
Expects(pGroup->m_pConditionSet != nullptr);
pFirstCondition = pGroup->m_pConditionSet->conditions;
bProcessPause = pGroup->m_pConditionSet->has_pause;
}

bool bIsIndirect = false;
bool bIsMultiLevelIndirect = false;
rc_typed_value_t value = {};
rc_eval_state_t oEvalState;
memset(&oEvalState, 0, sizeof(oEvalState));
oEvalState.peek = rc_peek_callback;
oEvalState.recall_value.type = RC_VALUE_TYPE_UNSIGNED;
oEvalState.recall_value.value.u32 = 0;

int nPassesLeft = (bProcessPause ? 2 : 1); //If there are pauses, they may have remembered values that need to be processed first, so we do [up to] two passes.
while (nPassesLeft > 0) {
rc_condition_t* pCondition = pFirstCondition;
gsl::index nConditionIndex = 0;
for (; pCondition != nullptr; pCondition = pCondition->next)
{
auto* vmCondition = pTriggerViewModel->Conditions().GetItemAt(nConditionIndex++);
if (!vmCondition)
break;

if ((pCondition->pause > 0) != bProcessPause) continue;

if (vmCondition == this)
{
// found the condition we're looking for
if (bIsMultiLevelIndirect && pPointerAddress != nullptr)
*pPointerAddress = NESTED_POINTER_ADDRESS;

return nAddress + oEvalState.add_address;
}

switch (pCondition->type)
{
case RC_CONDITION_ADD_ADDRESS:
// AddAddress after an AddAddress is a multi-level indirect
bIsMultiLevelIndirect = bIsIndirect;
break;

case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
// these can modify the Remembered value
break;

case RC_CONDITION_REMEMBER:
// Remember/Recall can be used in AddAddress chains
break;

default:
// other flags aren't part of the AddAddress chain
bIsIndirect = bIsMultiLevelIndirect = false;
oEvalState.add_address = 0;
oEvalState.add_value.type = RC_VALUE_TYPE_NONE;
continue;
}

if (bIsIndirect && pTrigger == nullptr)
{
// if this is part of a chain, we have to create a copy of the condition so we can point
// at copies of the indirect memrefs so the real delta values aren't modified.
rc_condition_t oCondition;
memcpy(&oCondition, pCondition, sizeof(oCondition));
rc_memref_t oSource{}, oTarget{};

if (IsIndirectMemref(pCondition->operand1))
{
memcpy(&oSource, pCondition->operand1.value.memref, sizeof(oSource));
oCondition.operand1.value.memref = &oSource;
}

if (IsIndirectMemref(pCondition->operand2))
{
memcpy(&oTarget, pCondition->operand2.value.memref, sizeof(oTarget));
oCondition.operand2.value.memref = &oTarget;
}

rc_evaluate_condition_value(&value, &oCondition, &oEvalState);
}
else
{
rc_evaluate_condition_value(&value, pCondition, &oEvalState);

if (pCondition->type == RC_CONDITION_ADD_ADDRESS)
{
// live AddAddress chain. capture the pointer value
if (pPointerAddress != nullptr && rc_operand_is_memref(&pCondition->operand1))
*pPointerAddress = (bIsIndirect) ? NESTED_POINTER_ADDRESS : pCondition->operand1.value.memref->address;
}
}

switch (pCondition->type)
{
case RC_CONDITION_ADD_ADDRESS:
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
oEvalState.add_address = value.value.u32;
bIsIndirect = true;
break;
rc_condition_t* pCondition = pFirstCondition;
gsl::index nConditionIndex = 0;
for (; pCondition != nullptr; pCondition = pCondition->next)
{
auto* vmCondition = pTriggerViewModel->Conditions().GetItemAt(nConditionIndex++);
if (!vmCondition)
break;

case RC_CONDITION_SUB_SOURCE:
rc_typed_value_negate(&value);
_FALLTHROUGH; // to RC_CONDITION_ADD_SOURCE
if (vmCondition == this)
{
if (rc_operand_is_memref(&pCondition->operand1))
return GetIndirectAddressFromOperand(&pCondition->operand1, nAddress, pPointerAddress);

case RC_CONDITION_ADD_SOURCE:
rc_typed_value_add(&oEvalState.add_value, &value);
oEvalState.add_address = 0;
bIsIndirect = bIsMultiLevelIndirect = false;
break;
if (rc_operand_is_memref(&pCondition->operand2))
return GetIndirectAddressFromOperand(&pCondition->operand2, nAddress, pPointerAddress);

case RC_CONDITION_REMEMBER:
rc_typed_value_add(&value, &oEvalState.add_value);
oEvalState.recall_value.type = value.type;
oEvalState.recall_value.value = value.value;
oEvalState.add_value.type = RC_VALUE_TYPE_NONE;
oEvalState.add_address = 0;
bIsIndirect = bIsMultiLevelIndirect = false;
break;
}
break;
}
nPassesLeft--;
bProcessPause = false; //pause pass is always first, so we are no longer to process pause logic
}

return nAddress;
Expand Down
11 changes: 8 additions & 3 deletions src/ui/viewmodels/TriggerViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,11 @@ void TriggerViewModel::PasteFromClipboard()
rc_init_parse_state(&parse, nullptr, nullptr, 0);
rc_memref_t* first_memref;
rc_init_parse_state_memrefs(&parse, &first_memref);
parse.is_value = IsValue();
std::string sTrigger = ra::Narrow(sClipboardText);
const char* memaddr = sTrigger.c_str();
Expects(memaddr != nullptr);
rc_parse_condset(&memaddr, &parse, IsValue());
rc_parse_condset(&memaddr, &parse);

const auto nSize = parse.offset;
if (nSize > 0 && *memaddr == 'S')
Expand All @@ -269,8 +270,9 @@ void TriggerViewModel::PasteFromClipboard()
sTriggerBuffer.resize(nSize);
rc_init_parse_state(&parse, sTriggerBuffer.data(), nullptr, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
parse.is_value = IsValue();
memaddr = sTrigger.c_str();
const rc_condset_t* pCondSet = rc_parse_condset(&memaddr, &parse, IsValue());
const rc_condset_t* pCondSet = rc_parse_condset(&memaddr, &parse);
Expects(pCondSet != nullptr);

m_vConditions.BeginUpdate();
Expand Down Expand Up @@ -1253,13 +1255,16 @@ void TriggerViewModel::UpdateConditionColors(const rc_trigger_t* pTrigger)
{
// when a condset is paused, processing stops when the first pause condition is true. only highlight it
bool bFirstPause = true;
const rc_condition_t* pPauseConditions = rc_condset_get_conditions(pSelectedGroup->m_pConditionSet);
const rc_condition_t* pEndPauseConditions = pPauseConditions + pSelectedGroup->m_pConditionSet->num_pause_conditions;

rc_condition_t* pCondition = pSelectedGroup->m_pConditionSet->conditions;
for (; pCondition != nullptr; pCondition = pCondition->next, ++nConditionIndex)
{
auto* vmCondition = m_vConditions.GetItemAt(nConditionIndex);
if (vmCondition != nullptr)
{
if (pCondition->pause && bFirstPause)
if (pCondition < pEndPauseConditions && pCondition > pPauseConditions && bFirstPause)
{
vmCondition->UpdateRowColor(pCondition);

Expand Down
22 changes: 10 additions & 12 deletions tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "CppUnitTest.h"

#include "services\AchievementRuntime.hh"

#include "ui\EditorTheme.hh"
#include "ui\viewmodels\TriggerConditionViewModel.hh"
#include "ui\viewmodels\TriggerViewModel.hh"
Expand All @@ -12,6 +14,8 @@
#include "tests\mocks\MockUserContext.hh"
#include "tests\ui\viewmodels\TriggerConditionAsserts.hh"

#include <rcheevos\src\rcheevos\rc_internal.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace ra {
Expand Down Expand Up @@ -1007,8 +1011,8 @@ TEST_CLASS(TriggerConditionViewModel_Tests)
Expects(pTrigger != nullptr);
vmTrigger.InitializeFrom(*pTrigger);

// manually update the memrefs - GetIndirectAddress won't if the trigger is not internal to the viewmodel.
pTrigger->memrefs->value.value = 1;
// manually update the memrefs as if rc_runtime_do_frame where called
rc_update_memref_values(pTrigger->memrefs, rc_peek_callback, nullptr);

const auto* pCondition1 = vmTrigger.Conditions().GetItemAt(0);
Expects(pCondition1 != nullptr);
Expand All @@ -1029,15 +1033,9 @@ TEST_CLASS(TriggerConditionViewModel_Tests)
// Calling GetTooltip on a viewmodel attached to an external trigger should not evaluate the memrefs.
// External memrefs should only be updated by the runtime to ensure delta values are correct.
vmTrigger.SetMemory({ 1 }, 3);
Assert::AreEqual(std::wstring(L"0x0001\r\n[No code note]"), pCondition1->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0003 (indirect)\r\n[No code note]"), pCondition2->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0006 (indirect)\r\n[No code note]"), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));

// memref should not have been updated
Assert::AreEqual(1U, pTrigger->memrefs->value.value);

// Manually update the memref as if rc_runtime_do_frame were called, then the tooltips will update
pTrigger->memrefs->value.value = 3;
rc_update_memref_values(pTrigger->memrefs, rc_peek_callback, nullptr);

// $0001 = 3, 3+2 = $0005, $0005 = 5, 5+3 = $0008
Assert::AreEqual(std::wstring(L"0x0001\r\n[No code note]"), pCondition1->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Expand Down Expand Up @@ -1104,7 +1102,7 @@ TEST_CLASS(TriggerConditionViewModel_Tests)

// $0001 = 3, 3+2 = $0005, $0005 = 5, 5+3 = $0008
vmTrigger.SetMemory({ 1 }, 3);
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\n[No code note]"), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\nFirst Level A\n +3=Second Level."), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0008 (indirect)\r\n[Nested pointer code note not supported]"), pCondition4->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
}

Expand Down Expand Up @@ -1208,7 +1206,7 @@ TEST_CLASS(TriggerConditionViewModel_Tests)

// $0001 = 3, 3+2 = $0005, $0005 = 5, 5+3 = $0008
vmTrigger.SetMemory({ 1 }, 3);
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\n[No code note]"), pCondition4->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\nFirst Level A\n +3=Second Level."), pCondition4->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0008 (indirect)\r\n[Nested pointer code note not supported]"), pCondition5->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
}

Expand Down Expand Up @@ -1244,7 +1242,7 @@ TEST_CLASS(TriggerConditionViewModel_Tests)

// $0001 = 3, 3+2 = $0005, $0005 = 5, 5+3 = $0008
vmTrigger.SetMemory({ 1 }, 3);
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\n[No code note]"), pCondition2->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\nFirst Level A\n +3=Second Level."), pCondition2->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
Assert::AreEqual(std::wstring(L"0x0008 (indirect)\r\n[Nested pointer code note not supported]"), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
}

Expand Down

0 comments on commit 8a26afb

Please sign in to comment.