diff --git a/rcheevos b/rcheevos index 1626e8e5..8d8ef920 160000 --- a/rcheevos +++ b/rcheevos @@ -1 +1 @@ -Subproject commit 1626e8e5074d01ad4d91afc0713e031065941024 +Subproject commit 8d8ef920e253f1286464771e81ce4cf7f4358eee diff --git a/src/rcheevos.vcxproj b/src/rcheevos.vcxproj index 951daa8a..ca60384c 100644 --- a/src/rcheevos.vcxproj +++ b/src/rcheevos.vcxproj @@ -206,6 +206,9 @@ + + + diff --git a/src/rcheevos.vcxproj.filters b/src/rcheevos.vcxproj.filters index 25f0a124..52a6d586 100644 --- a/src/rcheevos.vcxproj.filters +++ b/src/rcheevos.vcxproj.filters @@ -168,4 +168,9 @@ + + + rcheevos + + \ No newline at end of file diff --git a/src/ui/viewmodels/TriggerConditionViewModel.cpp b/src/ui/viewmodels/TriggerConditionViewModel.cpp index 611b84ef..90a295ee 100644 --- a/src/ui/viewmodels/TriggerConditionViewModel.cpp +++ b/src/ui/viewmodels/TriggerConditionViewModel.cpp @@ -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(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(operand.type); @@ -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(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(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, @@ -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 @@ -714,7 +767,6 @@ ra::ByteAddress TriggerConditionViewModel::GetIndirectAddress(ra::ByteAddress nA if (nIndex == 0) { pFirstCondition = pTrigger->requirement->conditions; - bProcessPause = pTrigger->requirement->has_pause; } else { @@ -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; diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp index 2619b833..4da274c0 100644 --- a/src/ui/viewmodels/TriggerViewModel.cpp +++ b/src/ui/viewmodels/TriggerViewModel.cpp @@ -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') @@ -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(); @@ -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); diff --git a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp index d06332cc..97aac1e5 100644 --- a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp +++ b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp @@ -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" @@ -12,6 +14,8 @@ #include "tests\mocks\MockUserContext.hh" #include "tests\ui\viewmodels\TriggerConditionAsserts.hh" +#include + using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace ra { @@ -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); @@ -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)); @@ -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)); } @@ -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)); } @@ -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)); }