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

upgrade rcheevos #1128

Merged
Merged
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
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
Loading