Repository: RetroAchievements/RAIntegration
Pull request #925 (2022-10-08 21:59:44)

allow rich presence monitor to be resized


Patch:
diff --git a/rcheevos b/rcheevos
index bbefee2e..e99fa55b 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1,1 +1,1 @@
-Subproject commit bbefee2ed023104c083092e82c5d14505805014e
+Subproject commit e99fa55b1cfeb665aab209cf0b3dd9970e9caa31
diff --git a/src/Exports.cpp b/src/Exports.cpp
index 1d369458..e5a9f741 100644
--- a/src/Exports.cpp
+++ b/src/Exports.cpp
@@ -298,7 +298,7 @@ static void HandleLoginResponse(const ra::api::Login::Response& response)
         ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>().RebuildMenu();
 
         // update the client title-bar to include the user name
-        _RA_UpdateAppTitle();
+        ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().Emulator.UpdateWindowTitle();
     }
     else if (!response.ErrorMessage.empty())
     {
@@ -408,9 +408,8 @@ API int CCONV _RA_OnLoadNewRom(const BYTE* pROM, unsigned int nROMSize)
 
 API void CCONV _RA_UpdateAppTitle(const char* sMessage)
 {
-    auto sTitle = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>().GetAppTitle(sMessage ? sMessage : "");
     auto& vmEmulator = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().Emulator;
-    vmEmulator.SetWindowTitle(sTitle);
+    vmEmulator.SetAppTitleMessage(sMessage);
 }
 
 API int _RA_IsOverlayFullyVisible()
diff --git a/src/RA_Defs.cpp b/src/RA_Defs.cpp
index 69930791..72aebe6a 100644
--- a/src/RA_Defs.cpp
+++ b/src/RA_Defs.cpp
@@ -76,6 +76,9 @@ std::wstring MemSizeFormat(unsigned nValue, MemSize nSize, MemFormat nFormat)
         case MemSize::MBF32:
             return U32ToFloatString(nValue, RC_MEMSIZE_MBF32);
 
+        case MemSize::MBF32LE:
+            return U32ToFloatString(nValue, RC_MEMSIZE_MBF32_LE);
+
         default:
             if (nFormat == MemFormat::Dec)
                 return std::to_wstring(nValue);
@@ -112,6 +115,9 @@ unsigned FloatToU32(float fValue, MemSize nFloatType) noexcept
 
     nValue += 0x02000000; // adjust to 129 base
 
+    if (nFloatType == MemSize::MBF32LE)
+        return nValue;
+
     return ((nValue & 0xFF000000) >> 24) | // convert to big endian
         ((nValue & 0x00FF0000) >> 8) |
         ((nValue & 0x0000FF00) << 8) |
@@ -131,10 +137,13 @@ float U32ToFloat(unsigned nValue, MemSize nFloatType) noexcept
 
     if (nFloatType != MemSize::Float)
     {
-        nValue = ((nValue & 0xFF000000) >> 24) | // convert to big endian
-            ((nValue & 0x00FF0000) >> 8) |
-            ((nValue & 0x0000FF00) << 8) |
-            ((nValue & 0x000000FF) << 24);
+        if (nFloatType == MemSize::MBF32)
+        {
+            nValue = ((nValue & 0xFF000000) >> 24) | // convert to big endian
+                ((nValue & 0x00FF0000) >> 8) |
+                ((nValue & 0x0000FF00) << 8) |
+                ((nValue & 0x000000FF) << 24);
+        }
 
         // MBF32 puts the sign after the exponent, uses a 129 base instead of 127, and stores in big endian
         nValue -= 0x02000000; // adjust to 129 base
diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj
index 5b0417be..a5ac7409 100644
--- a/src/RA_Integration.vcxproj
+++ b/src/RA_Integration.vcxproj
@@ -111,6 +111,7 @@
     <ClCompile Include="ui\viewmodels\BrokenAchievementsViewModel.cpp" />
     <ClCompile Include="ui\viewmodels\ChallengeIndicatorViewModel.cpp" />
     <ClCompile Include="ui\viewmodels\CodeNotesViewModel.cpp" />
+    <ClCompile Include="ui\viewmodels\EmulatorViewModel.cpp" />
     <ClCompile Include="ui\viewmodels\FileDialogViewModel.cpp" />
     <ClCompile Include="ui\viewmodels\GameChecksumViewModel.cpp" />
     <ClCompile Include="ui\viewmodels\IntegrationMenuViewModel.cpp" />
diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters
index b48e6f67..ab0501f2 100644
--- a/src/RA_Integration.vcxproj.filters
+++ b/src/RA_Integration.vcxproj.filters
@@ -393,6 +393,9 @@
     <ClCompile Include="data\models\CodeNotesModel.cpp">
       <Filter>Data\Models</Filter>
     </ClCompile>
+    <ClCompile Include="ui\viewmodels\EmulatorViewModel.cpp">
+      <Filter>UI\ViewModels</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="RA_Resource.h">
diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc
index 6240c3e5..bd85a7f5 100644
--- a/src/RA_Shared.rc
+++ b/src/RA_Shared.rc
@@ -230,7 +230,7 @@ BEGIN
 END
 
 IDD_RA_RICHPRESENCE DIALOGEX 0, 0, 149, 50
-STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
 CAPTION "Rich Presence Monitor"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
diff --git a/src/data/Types.hh b/src/data/Types.hh
index c66bd745..8201370b 100644
--- a/src/data/Types.hh
+++ b/src/data/Types.hh
@@ -24,6 +24,7 @@ enum class MemSize : uint8_t
     ThirtyTwoBitBigEndian,
     Float,
     MBF32,
+    MBF32LE,
 
     // extended sizes not supported by rcheevos
     Unknown,
@@ -74,6 +75,7 @@ constexpr unsigned int MemSizeBits(MemSize nSize)
         case MemSize::ThirtyTwoBitBigEndian:
         case MemSize::Float:
         case MemSize::MBF32:
+        case MemSize::MBF32LE:
             return 32;
 
         case MemSize::EightBit:
@@ -124,6 +126,7 @@ constexpr unsigned int MemSizeBytes(MemSize nSize)
         case MemSize::ThirtyTwoBitBigEndian:
         case MemSize::Float:
         case MemSize::MBF32:
+        case MemSize::MBF32LE:
             return 4;
 
         case MemSize::SixteenBit:
diff --git a/src/data/context/EmulatorContext.cpp b/src/data/context/EmulatorContext.cpp
index a98afa0b..14cbd3bb 100644
--- a/src/data/context/EmulatorContext.cpp
+++ b/src/data/context/EmulatorContext.cpp
@@ -834,6 +834,7 @@ uint32_t EmulatorContext::ReadMemory(ra::ByteAddress nAddress, MemSize nSize) co
         }
         case MemSize::Float:
         case MemSize::MBF32:
+        case MemSize::MBF32LE:
         case MemSize::ThirtyTwoBit:
         {
             uint8_t buffer[4];
@@ -917,6 +918,7 @@ void EmulatorContext::WriteMemory(ra::ByteAddress nAddress, MemSize nSize, uint3
         case MemSize::ThirtyTwoBit:
         case MemSize::Float: // assumes the value has already been encoded into a 32-bit value
         case MemSize::MBF32: // assumes the value has already been encoded into a 32-bit value
+        case MemSize::MBF32LE: // assumes the value has already been encoded into a 32-bit value
             WriteMemoryByte(nAddress, nValue & 0xFF);
             nValue >>= 8;
             WriteMemoryByte(++nAddress, nValue & 0xFF);
diff --git a/src/data/context/EmulatorContext.hh b/src/data/context/EmulatorContext.hh
index 97752d5a..d63d8ce1 100644
--- a/src/data/context/EmulatorContext.hh
+++ b/src/data/context/EmulatorContext.hh
@@ -100,7 +100,7 @@ public:
     /// </summary>
     /// <param name="sMessage">A custom message provided by the emulator to also display in the title bar.</param>
     std::wstring GetAppTitle(const std::string& sMessage) const;
-    
+
     /// <summary>
     /// Gets the game title from the emulator.
     /// </summary>
diff --git a/src/data/context/UserContext.cpp b/src/data/context/UserContext.cpp
index 3441e799..26d5e94c 100644
--- a/src/data/context/UserContext.cpp
+++ b/src/data/context/UserContext.cpp
@@ -11,6 +11,7 @@
 
 #include "ui\viewmodels\MessageBoxViewModel.hh"
 #include "ui\viewmodels\OverlayManager.hh"
+#include "ui\viewmodels\WindowManager.hh"
 
 namespace ra {
 namespace data {
@@ -35,9 +36,8 @@ void UserContext::Logout()
         pOverlayManager.HideOverlay();
 
         ra::services::ServiceLocator::Get<ra::services::IConfiguration>().Save();
-#ifndef RA_UTEST
-        _RA_UpdateAppTitle();
-#endif
+
+        ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().Emulator.UpdateWindowTitle();
         ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>().RebuildMenu();
 
         ra::ui::viewmodels::MessageBoxViewModel::ShowInfoMessage(L"You are now logged out.");
diff --git a/src/data/models/CodeNotesModel.cpp b/src/data/models/CodeNotesModel.cpp
index f5ed6dcd..4bb57c14 100644
--- a/src/data/models/CodeNotesModel.cpp
+++ b/src/data/models/CodeNotesModel.cpp
@@ -146,7 +146,6 @@ void CodeNotesModel::ExtractSize(CodeNote& pNote)
             {
                 pNote.Bytes = 4;
                 pNote.MemSize = MemSize::MBF32;
-                return;
             }
             else if (sBits == L"40")
             {
@@ -154,9 +153,32 @@ void CodeNotesModel::ExtractSize(CodeNote& pNote)
                 // it can be read as an MBF32 value with only the loss of the smallest 8-bits of precision.
                 pNote.MemSize = MemSize::MBF32;
                 pNote.Bytes = 5;
-                return;
             }
-            continue;
+            else
+            {
+                continue;
+            }
+
+            // check for LE (little endian)
+            nIndex += 4;
+            while (nIndex < pNote.Note.length() && (pNote.Note.at(nIndex) == ' ' || pNote.Note.at(nIndex) == '-'))
+                ++nIndex;
+            if (nIndex + 1 < pNote.Note.length())
+            {
+                c = pNote.Note.at(nIndex);
+                if (c == 'L' || c == 'l')
+                {
+                    c = pNote.Note.at(++nIndex);
+                    if (c == 'E' || c == 'e')
+                    {
+                        nIndex++;
+                        if (nIndex == pNote.Note.length() || !std::isalpha(pNote.Note.at(nIndex)))
+                            pNote.MemSize = MemSize::MBF32LE;
+                    }
+                }
+            }
+
+            return;
         }
         else
         {
diff --git a/src/data/models/TriggerValidation.cpp b/src/data/models/TriggerValidation.cpp
index 39c8e014..917aa9ff 100644
--- a/src/data/models/TriggerValidation.cpp
+++ b/src/data/models/TriggerValidation.cpp
@@ -36,6 +36,7 @@ MemSize TriggerValidation::MapRcheevosMemSize(char nSize) noexcept
         case RC_MEMSIZE_32_BITS_BE: return MemSize::ThirtyTwoBitBigEndian;
         case RC_MEMSIZE_FLOAT: return MemSize::Float;
         case RC_MEMSIZE_MBF32: return MemSize::MBF32;
+        case RC_MEMSIZE_MBF32_LE: return MemSize::MBF32LE;
         default:
             assert(!"Unsupported operand size");
             return MemSize::EightBit;
diff --git a/src/services/SearchResults.cpp b/src/services/SearchResults.cpp
index e84f6840..ece8a8c4 100644
--- a/src/services/SearchResults.cpp
+++ b/src/services/SearchResults.cpp
@@ -1352,6 +1352,30 @@ protected:
     }
 };
 
+class MBF32LESearchImpl : public FloatSearchImpl
+{
+public:
+    MemSize GetMemSize() const noexcept override { return MemSize::MBF32LE; }
+
+protected:
+    GSL_SUPPRESS_TYPE1
+    float BuildFloatValue(const unsigned char* ptr) const noexcept override
+    {
+        GSL_SUPPRESS_F6 Expects(ptr != nullptr);
+        rc_typed_value_t value;
+        value.type = RC_VALUE_TYPE_UNSIGNED;
+        value.value.u32 = *reinterpret_cast<const unsigned int*>(ptr);
+        rc_transform_memref_value(&value, RC_MEMSIZE_MBF32_LE);
+
+        return value.value.f32;
+    }
+
+    unsigned int DeconstructFloatValue(float fValue) const noexcept override
+    {
+        return ra::data::FloatToU32(fValue, MemSize::MBF32LE);
+    }
+};
+
 
 static FourBitSearchImpl s_pFourBitSearchImpl;
 static EightBitSearchImpl s_pEightBitSearchImpl;
@@ -1364,6 +1388,7 @@ static ThirtyTwoBitBigEndianSearchImpl s_pThirtyTwoBitBigEndianSearchImpl;
 static AsciiTextSearchImpl s_pAsciiTextSearchImpl;
 static FloatSearchImpl s_pFloatSearchImpl;
 static MBF32SearchImpl s_pMBF32SearchImpl;
+static MBF32LESearchImpl s_pMBF32LESearchImpl;
 
 uint8_t* MemBlock::AllocateMatchingAddresses() noexcept
 {
@@ -1566,6 +1591,9 @@ void SearchResults::Initialize(ra::ByteAddress nAddress, size_t nBytes, SearchTy
         case SearchType::MBF32:
             m_pImpl = &ra::services::impl::s_pMBF32SearchImpl;
             break;
+        case SearchType::MBF32LE:
+            m_pImpl = &ra::services::impl::s_pMBF32LESearchImpl;
+            break;
     }
 
     const unsigned int nPadding = m_pImpl->GetPadding();
diff --git a/src/services/SearchResults.h b/src/services/SearchResults.h
index 3499ef77..171026e0 100644
--- a/src/services/SearchResults.h
+++ b/src/services/SearchResults.h
@@ -21,6 +21,7 @@ enum class SearchType
     ThirtyTwoBitBigEndian,
     Float,
     MBF32,
+    MBF32LE,
     AsciiText,
 };
 
diff --git a/src/ui/viewmodels/AssetListViewModel.cpp b/src/ui/viewmodels/AssetListViewModel.cpp
index aaf49f15..6ef9529e 100644
--- a/src/ui/viewmodels/AssetListViewModel.cpp
+++ b/src/ui/viewmodels/AssetListViewModel.cpp
@@ -1180,8 +1180,8 @@ static std::wstring ValidateCondSet(const rc_condset_t* pCondSet)
         /* rc_condition_t validation goes here */
 #if RA_INTEGRATION_VERSION_LESS_THAN(1,0,5)
 #define VALIDATE_PRERELEASE_FUNCTIONALITY
-//        if (pCondition->operand1.size == RC_MEMSIZE_MBF32_LE || pCondition->operand2.size == RC_MEMSIZE_MBF32_LE)
-//            return L"MBF32 LE size is pre-release functionality";
+        if (pCondition->operand1.size == RC_MEMSIZE_MBF32_LE || pCondition->operand2.size == RC_MEMSIZE_MBF32_LE)
+            return L"MBF32 LE size is pre-release functionality";
 #endif
     }
 
diff --git a/src/ui/viewmodels/EmulatorViewModel.cpp b/src/ui/viewmodels/EmulatorViewModel.cpp
new file mode 100644
index 00000000..ab3b6ab4
--- /dev/null
+++ b/src/ui/viewmodels/EmulatorViewModel.cpp
@@ -0,0 +1,22 @@
+#include "EmulatorViewModel.hh"
+
+#include "data/context/EmulatorContext.hh"
+
+#include "services/ServiceLocator.hh"
+
+namespace ra {
+namespace ui {
+namespace viewmodels {
+
+void EmulatorViewModel::UpdateWindowTitle()
+{
+    if (!m_bAppTitleManaged)
+        return;
+
+    auto sTitle = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>().GetAppTitle(m_sAppTitleMessage);
+    SetWindowTitle(sTitle);
+}
+
+} // namespace viewmodels
+} // namespace ui
+} // namespace ra
diff --git a/src/ui/viewmodels/EmulatorViewModel.hh b/src/ui/viewmodels/EmulatorViewModel.hh
index 118e35e2..629d72db 100644
--- a/src/ui/viewmodels/EmulatorViewModel.hh
+++ b/src/ui/viewmodels/EmulatorViewModel.hh
@@ -10,6 +10,25 @@ namespace viewmodels {
 
 class EmulatorViewModel : public WindowViewModelBase
 {
+public:
+    /// <summary>
+    /// Sets the additional text to display in the application title bar.
+    /// </summary>
+    void SetAppTitleMessage(const std::string& sValue)
+    {
+        m_bAppTitleManaged = true;
+        m_sAppTitleMessage = sValue;
+
+        UpdateWindowTitle();
+    }
+
+    bool IsAppTitleManaged() const noexcept { return m_bAppTitleManaged; }
+
+    void UpdateWindowTitle();
+
+private:
+    bool m_bAppTitleManaged = false;
+    std::string m_sAppTitleMessage;
 };
 
 } // namespace viewmodels
diff --git a/src/ui/viewmodels/LoginViewModel.cpp b/src/ui/viewmodels/LoginViewModel.cpp
index 470e84ba..b8bd7db8 100644
--- a/src/ui/viewmodels/LoginViewModel.cpp
+++ b/src/ui/viewmodels/LoginViewModel.cpp
@@ -13,6 +13,7 @@
 #include "services\IConfiguration.hh"
 
 #include "ui\viewmodels\MessageBoxViewModel.hh"
+#include "ui\viewmodels\WindowManager.hh"
 
 namespace ra {
 namespace ui {
@@ -79,9 +80,7 @@ bool LoginViewModel::Login() const
         std::wstring(L"Successfully logged in as ") + ra::Widen(response.Username));
 
     ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>().RebuildMenu();
-#ifndef RA_UTEST
-    _RA_UpdateAppTitle();
-#endif
+    ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().Emulator.UpdateWindowTitle();
 
     return true;
 }
diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
index 8e7dc735..db25628a 100644
--- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
+++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
@@ -62,6 +62,7 @@ MemoryBookmarksViewModel::MemoryBookmarksViewModel() noexcept
     m_vSizes.Add(ra::etoi(MemSize::Nibble_Upper), L"Upper4");
     m_vSizes.Add(ra::etoi(MemSize::Float), L"Float");
     m_vSizes.Add(ra::etoi(MemSize::MBF32), L"MBF32");
+    m_vSizes.Add(ra::etoi(MemSize::MBF32LE), L"MBF32 LE");
     m_vSizes.Add(ra::etoi(MemSize::Text), L"ASCII");
 
     m_vFormats.Add(ra::etoi(MemFormat::Hex), L"Hex");
@@ -244,6 +245,7 @@ bool MemoryBookmarksViewModel::MemoryBookmarkViewModel::SetCurrentValue(const st
     {
         case MemSize::Float:
         case MemSize::MBF32:
+        case MemSize::MBF32LE:
             float fValue;
             if (!ra::ParseFloat(sValue, fValue, sError))
                 return false;
diff --git a/src/ui/viewmodels/MemorySearchViewModel.cpp b/src/ui/viewmodels/MemorySearchViewModel.cpp
index bb7c06f9..32c2d572 100644
--- a/src/ui/viewmodels/MemorySearchViewModel.cpp
+++ b/src/ui/viewmodels/MemorySearchViewModel.cpp
@@ -101,6 +101,7 @@ MemorySearchViewModel::MemorySearchViewModel()
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::ThirtyTwoBitBigEndian), L"32-bit BE");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::Float), L"Float");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::MBF32), L"MBF32");
+    m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::MBF32LE), L"MBF32 LE");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::AsciiText), L"ASCII Text");
 
     m_vComparisonTypes.Add(ra::etoi(ComparisonType::Equals), L"=");
@@ -709,7 +710,6 @@ void MemorySearchViewModel::UpdateResults()
     const auto& pConsoleContext = ra::services::ServiceLocator::Get<ra::data::context::ConsoleContext>();
     const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
     const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes();
-    Expects(pCodeNotes != nullptr);
 
     m_vResults.RemoveNotifyTarget(*this);
     m_vResults.BeginUpdate();
@@ -747,7 +747,7 @@ void MemorySearchViewModel::UpdateResults()
 
         UpdateResult(*pRow, pCurrentResults.pResults, pResult, true, pEmulatorContext);
 
-        const auto pCodeNote = pCodeNotes->FindCodeNote(pResult.nAddress, pResult.nSize);
+        const auto pCodeNote = (pCodeNotes != nullptr) ? pCodeNotes->FindCodeNote(pResult.nAddress, pResult.nSize) : std::wstring(L"");
         if (!pCodeNote.empty())
         {
             pRow->bHasCodeNote = true;
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.cpp b/src/ui/viewmodels/TriggerConditionViewModel.cpp
index 3ea40f01..9c5cc86e 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.cpp
+++ b/src/ui/viewmodels/TriggerConditionViewModel.cpp
@@ -251,6 +251,13 @@ void TriggerConditionViewModel::SerializeAppendOperand(std::string& sBuffer, Tri
             sBuffer.push_back('M');
             break;
 
+        case MemSize::MBF32LE:
+            sBuffer.pop_back();
+            sBuffer.pop_back();
+            sBuffer.push_back('f');
+            sBuffer.push_back('L');
+            break;
+
         default:
             assert(!"Unknown memory size");
             break;
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 81b7e298..1ac6cf69 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -70,6 +70,7 @@ TriggerViewModel::TriggerViewModel() noexcept
     m_vOperandSizes.Add(ra::etoi(MemSize::BitCount), L"BitCount");
     m_vOperandSizes.Add(ra::etoi(MemSize::Float), L"Float");
     m_vOperandSizes.Add(ra::etoi(MemSize::MBF32), L"MBF32");
+    m_vOperandSizes.Add(ra::etoi(MemSize::MBF32LE), L"MBF32 LE");
 
     m_vOperatorTypes.Add(ra::etoi(TriggerOperatorType::Equals), L"=");
     m_vOperatorTypes.Add(ra::etoi(TriggerOperatorType::LessThan), L"<");
@@ -390,6 +391,7 @@ void TriggerViewModel::NewCondition()
     {
         case MemSize::Float:
         case MemSize::MBF32:
+        case MemSize::MBF32LE:
             vmCondition.SetTargetType(TriggerOperandType::Float);
             vmCondition.SetTargetValue(ra::data::U32ToFloat(nValue, nSize));
             break;
diff --git a/src/ui/win32/MemoryBookmarksDialog.cpp b/src/ui/win32/MemoryBookmarksDialog.cpp
index eb5b6fcb..065757dd 100644
--- a/src/ui/win32/MemoryBookmarksDialog.cpp
+++ b/src/ui/win32/MemoryBookmarksDialog.cpp
@@ -159,6 +159,7 @@ private:
         {
             case MemSize::Float:
             case MemSize::MBF32:
+            case MemSize::MBF32LE:
             case MemSize::Text:
                 return true;
 
@@ -198,7 +199,7 @@ MemoryBookmarksDialog::MemoryBookmarksDialog(MemoryBookmarksViewModel& vmMemoryB
     auto pSizeColumn = std::make_unique<ra::ui::win32::bindings::GridLookupColumnBinding>(
         MemoryBookmarksViewModel::MemoryBookmarkViewModel::SizeProperty, vmMemoryBookmarks.Sizes());
     pSizeColumn->SetHeader(L"Size");
-    pSizeColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 56);
+    pSizeColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 62);
     pSizeColumn->SetAlignment(ra::ui::RelativePosition::Far);
     pSizeColumn->SetReadOnly(false);
     m_bindBookmarks.BindColumn(2, std::move(pSizeColumn));
diff --git a/src/ui/win32/RichPresenceDialog.cpp b/src/ui/win32/RichPresenceDialog.cpp
index e94c1a61..e966c20a 100644
--- a/src/ui/win32/RichPresenceDialog.cpp
+++ b/src/ui/win32/RichPresenceDialog.cpp
@@ -44,6 +44,10 @@ RichPresenceDialog::RichPresenceDialog(ra::ui::viewmodels::RichPresenceMonitorVi
     m_bindWindow.SetInitialPosition(RelativePosition::After, RelativePosition::Far, "Rich Presence Monitor");
     m_bindWindow.BindLabel(IDC_RA_RICHPRESENCERESULTTEXT,
                            ra::ui::viewmodels::RichPresenceMonitorViewModel::DisplayStringProperty);
+
+    using namespace ra::bitwise_ops;
+    SetAnchor(IDC_RA_RICHPRESENCERESULTTEXT, Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right);
+    SetMinimumSize(240, 90);
 }
 
 RichPresenceDialog::~RichPresenceDialog() noexcept
diff --git a/src/ui/win32/bindings/WindowBinding.cpp b/src/ui/win32/bindings/WindowBinding.cpp
index 3985dc9f..3f68361a 100644
--- a/src/ui/win32/bindings/WindowBinding.cpp
+++ b/src/ui/win32/bindings/WindowBinding.cpp
@@ -26,7 +26,7 @@ void WindowBinding::SetHWND(DialogBase* pDialog, HWND hWnd)
     if (m_hWnd)
     {
         // immediately push values from the viewmodel to the UI
-        SetWindowTextW(m_hWnd, GetValue(WindowViewModelBase::WindowTitleProperty).c_str());
+        UpdateAppTitle();
 
         for (auto& pIter : m_mLabelBindings)
         {
@@ -95,6 +95,13 @@ void WindowBinding::SetHWND(DialogBase* pDialog, HWND hWnd)
     }
 }
 
+void WindowBinding::UpdateAppTitle()
+{
+    const auto& pEmulatorViewModel = ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>().Emulator;
+    if (pEmulatorViewModel.IsAppTitleManaged())
+        SetWindowTextW(m_hWnd, GetValue(WindowViewModelBase::WindowTitleProperty).c_str());
+}
+
 void WindowBinding::SetInitialPosition(RelativePosition nDefaultHorizontalLocation, RelativePosition nDefaultVerticalLocation, const char* sSizeAndPositionKey)
 {
     if (sSizeAndPositionKey)
@@ -278,7 +285,7 @@ void WindowBinding::OnViewModelStringValueChanged(const StringModelProperty::Cha
 {
     if (args.Property == WindowViewModelBase::WindowTitleProperty)
     {
-        SetWindowTextW(m_hWnd, args.tNewValue.c_str());
+        UpdateAppTitle();
         return;
     }
 
diff --git a/src/ui/win32/bindings/WindowBinding.hh b/src/ui/win32/bindings/WindowBinding.hh
index 5fe09544..e0afff74 100644
--- a/src/ui/win32/bindings/WindowBinding.hh
+++ b/src/ui/win32/bindings/WindowBinding.hh
@@ -132,6 +132,7 @@ protected:
     GSL_SUPPRESS_F6 void OnViewModelBoolValueChanged(const BoolModelProperty::ChangeArgs& args) noexcept override;
 
     void RestoreSizeAndPosition();
+    void UpdateAppTitle();
 
 private:
     std::unordered_map<int, int> m_mLabelBindings;


Test patch:
diff --git a/tests/Exports_Tests.cpp b/tests/Exports_Tests.cpp
index 7a87aa06..18566746 100644
--- a/tests/Exports_Tests.cpp
+++ b/tests/Exports_Tests.cpp
@@ -3,6 +3,7 @@
 #include "Exports.hh"
 
 #include "RA_BuildVer.h"
+#include "RA_Emulators.h"
 #include "RA_Resource.h"
 
 #include "tests\mocks\MockAudioSystem.hh"
@@ -190,6 +191,65 @@ public:
         // menu
         Assert::IsTrue(bWasMenuRebuilt);
 
+        // titlebar (should not be updated unless _RA_UpdateAppTitle was called first)
+        Assert::AreEqual(std::wstring(L"Window"), harness.mockWindowManager.Emulator.GetWindowTitle());
+    }
+
+    TEST_METHOD(TestAttemptLoginSuccessTitlebar)
+    {
+        AttemptLoginHarness harness;
+
+        bool bWasMenuRebuilt = false;
+        harness.mockEmulatorContext.SetRebuildMenuFunction([&bWasMenuRebuilt]() { bWasMenuRebuilt = true; });
+        harness.mockEmulatorContext.MockClient("RATests", "0.1.2.0");
+
+        bool bLoggedIn = false;
+        harness.mockServer.HandleRequest<api::Login>([&bLoggedIn](const ra::api::Login::Request& request, ra::api::Login::Response& response)
+        {
+            Assert::AreEqual(std::string("User"), request.Username);
+            Assert::AreEqual(std::string("ApiToken"), request.ApiToken);
+            bLoggedIn = true;
+
+            response.Username = "User";
+            response.DisplayName = "UserDisplay";
+            response.ApiToken = "ApiToken";
+            response.Score = 12345U;
+            response.Result = ra::api::ApiResult::Success;
+            return true;
+        });
+
+        harness.mockConfiguration.SetUsername("User");
+        harness.mockConfiguration.SetApiToken("ApiToken");
+
+        Assert::IsFalse(harness.mockUserContext.IsLoggedIn());
+
+        _RA_UpdateAppTitle(""); // enable automatic update of titlebar
+        _RA_AttemptLogin(true);
+
+        // user context
+        Assert::IsTrue(harness.mockUserContext.IsLoggedIn());
+        Assert::AreEqual(std::string("User"), harness.mockUserContext.GetUsername());
+        Assert::AreEqual(std::string("UserDisplay"), harness.mockUserContext.GetDisplayName());
+        Assert::AreEqual(std::string("ApiToken"), harness.mockUserContext.GetApiToken());
+        Assert::AreEqual(12345U, harness.mockUserContext.GetScore());
+
+        // session context
+        Assert::AreEqual(std::wstring(L"User"), harness.mockSessionTracker.GetUsername());
+
+        // popup notification and sound
+        Assert::IsTrue(harness.mockAudioSystem.WasAudioFilePlayed(L"Overlay\\login.wav"));
+        const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
+        Assert::IsNotNull(pPopup);
+        Ensures(pPopup != nullptr);
+        Assert::AreEqual(std::wstring(L"Welcome UserDisplay"), pPopup->GetTitle());
+        Assert::AreEqual(std::wstring(L"You have 0 new messages"), pPopup->GetDescription());
+        Assert::AreEqual(std::wstring(L"12345 points"), pPopup->GetDetail());
+        Assert::AreEqual(ra::ui::ImageType::UserPic, pPopup->GetImage().Type());
+        Assert::AreEqual(std::string("User"), pPopup->GetImage().Name());
+
+        // menu
+        Assert::IsTrue(bWasMenuRebuilt);
+
         // titlebar
         Assert::AreEqual(std::wstring(L"RATests - 0.1 - UserDisplay []"), harness.mockWindowManager.Emulator.GetWindowTitle());
     }
@@ -1053,6 +1113,29 @@ private:
         AssertMenuItem(&menu[19], IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem");
         AssertMenuItem(&menu[20], IDM_RA_GETROMCHECKSUM, L"View Game H&ash");
     }
+
+    TEST_METHOD(TestUpdateAppTitle)
+    {
+        ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        ra::data::context::mocks::MockUserContext mockUserContext;
+        ra::services::mocks::MockConfiguration mockConfiguration;
+
+        mockUserContext.Initialize("User", "TOKEN");
+        mockEmulatorContext.MockClient("MyClient", "1.2.3");
+        mockConfiguration.SetHostName("retroachievements.org");
+
+        Assert::AreEqual(std::wstring(L"Window"), mockWindowManager.Emulator.GetWindowTitle());
+
+        _RA_UpdateAppTitle("");
+        Assert::AreEqual(std::wstring(L"MyClient - 1.2 - User_"), mockWindowManager.Emulator.GetWindowTitle());
+
+        _RA_UpdateAppTitle("MyGame");
+        Assert::AreEqual(std::wstring(L"MyClient - 1.2 - MyGame - User_"), mockWindowManager.Emulator.GetWindowTitle());
+
+        _RA_UpdateAppTitle("");
+        Assert::AreEqual(std::wstring(L"MyClient - 1.2 - User_"), mockWindowManager.Emulator.GetWindowTitle());
+    }
 };
 
 } // namespace tests
diff --git a/tests/RA_Integration.Tests.vcxproj b/tests/RA_Integration.Tests.vcxproj
index e3380f7b..78c10887 100644
--- a/tests/RA_Integration.Tests.vcxproj
+++ b/tests/RA_Integration.Tests.vcxproj
@@ -418,6 +418,7 @@
     <ClCompile Include="..\src\ui\viewmodels\BrokenAchievementsViewModel.cpp" />
     <ClCompile Include="..\src\ui\viewmodels\ChallengeIndicatorViewModel.cpp" />
     <ClCompile Include="..\src\ui\viewmodels\CodeNotesViewModel.cpp" />
+    <ClCompile Include="..\src\ui\viewmodels\EmulatorViewModel.cpp" />
     <ClCompile Include="..\src\ui\viewmodels\FileDialogViewModel.cpp" />
     <ClCompile Include="..\src\ui\viewmodels\IntegrationMenuViewModel.cpp" />
     <ClCompile Include="..\src\ui\viewmodels\LookupItemViewModel.cpp" />
diff --git a/tests/RA_Integration.Tests.vcxproj.filters b/tests/RA_Integration.Tests.vcxproj.filters
index 2a4ca9f3..54420dda 100644
--- a/tests/RA_Integration.Tests.vcxproj.filters
+++ b/tests/RA_Integration.Tests.vcxproj.filters
@@ -444,6 +444,9 @@
     <ClCompile Include="..\src\data\models\CodeNotesModel.cpp">
       <Filter>Code</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\ui\viewmodels\EmulatorViewModel.cpp">
+      <Filter>Code</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <None Include="base.props" />
diff --git a/tests/RA_UnitTestHelpers.h b/tests/RA_UnitTestHelpers.h
index ca5538da..89e54764 100644
--- a/tests/RA_UnitTestHelpers.h
+++ b/tests/RA_UnitTestHelpers.h
@@ -81,6 +81,8 @@ std::wstring ToString<MemSize>(const MemSize& t)
             return L"Float";
         case MemSize::MBF32:
             return L"MBF32";
+        case MemSize::MBF32LE:
+            return L"MBF32LE";
         case MemSize::Text:
             return L"Text";
         case MemSize::Unknown:
diff --git a/tests/data/models/CodeNotesModel_Tests.cpp b/tests/data/models/CodeNotesModel_Tests.cpp
index c99ee908..3e156aa5 100644
--- a/tests/data/models/CodeNotesModel_Tests.cpp
+++ b/tests/data/models/CodeNotesModel_Tests.cpp
@@ -237,6 +237,8 @@ public:
         TestCodeNoteSize(L"[40-bit MBF] Test", 5U, MemSize::MBF32);
         TestCodeNoteSize(L"[MBF] Test", 1U, MemSize::Unknown);
         TestCodeNoteSize(L"Test MBF32", 4U, MemSize::MBF32);
+        TestCodeNoteSize(L"[MBF32 LE] Test", 4U, MemSize::MBF32LE);
+        TestCodeNoteSize(L"[MBF40-LE] Test", 5U, MemSize::MBF32LE);
 
         TestCodeNoteSize(L"42=bitten", 1U, MemSize::Unknown);
         TestCodeNoteSize(L"42-bitten", 1U, MemSize::Unknown);
diff --git a/tests/services/SearchResults_Tests.cpp b/tests/services/SearchResults_Tests.cpp
index 763e7629..9371f583 100644
--- a/tests/services/SearchResults_Tests.cpp
+++ b/tests/services/SearchResults_Tests.cpp
@@ -1605,6 +1605,38 @@ public:
         Assert::AreEqual(0x00008080U, result.nValue);
     }
 
+    TEST_METHOD(TestInitializeFromMemoryMBF32LE)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x46, 0x87, 0x00, 0x00, 0x80, 0x80 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32LE);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        Assert::IsTrue(results.ContainsAddress(0U));
+        Assert::IsTrue(results.ContainsAddress(1U));
+        Assert::IsTrue(results.ContainsAddress(2U));
+        Assert::IsTrue(results.ContainsAddress(3U));
+        Assert::IsTrue(results.ContainsAddress(4U));
+        Assert::IsFalse(results.ContainsAddress(5U));
+        Assert::IsFalse(results.ContainsAddress(6U));
+        Assert::IsFalse(results.ContainsAddress(7U));
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results.GetMatchingAddress(0U, result));
+        Assert::AreEqual(0U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32LE, result.nSize);
+        Assert::AreEqual(0x87460000U, result.nValue);
+
+        Assert::IsTrue(results.GetMatchingAddress(4U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32LE, result.nSize);
+        Assert::AreEqual(0x80800000U, result.nValue);
+    }
+
     TEST_METHOD(TestInitializeFromResultsAsciiText)
     {
         std::array<unsigned char, 36> memory{
@@ -1845,6 +1877,52 @@ public:
         Assert::AreEqual(0x00008000U, result.nValue);
     }
 
+    TEST_METHOD(TestInitializeFromResultsMBF32LEEqualsConstant)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x46, 0x87, 0x00, 0x00, 0x80, 0x80 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32LE);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::Equals, SearchFilterType::Constant, L"-0.5");
+        Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(0U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32LE, result.nSize);
+        Assert::AreEqual(0x80800000U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromResultsMBF32LEGreaterThanLast)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x46, 0x87, 0x00, 0x00, 0x80, 0x80 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32LE);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        memory.at(7) = 0x00; // -0.5 -> -0.0
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::GreaterThan, SearchFilterType::LastKnownValue, L"");
+        Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(0U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32LE, result.nSize);
+        Assert::AreEqual(0x00800000U, result.nValue);
+    }
+
     TEST_METHOD(TestCopyConstructor)
     {
         std::array<unsigned char, 5> memory{0x00, 0x12, 0x34, 0xAB, 0x56};
@@ -1953,7 +2031,7 @@ public:
 
         Assert::AreEqual(std::wstring(L"-2.0"), results1.GetFormattedValue(0U, MemSize::Float));
         Assert::AreEqual(std::wstring(L"12.375"), results1.GetFormattedValue(4U, MemSize::Float));
-        Assert::AreEqual(std::wstring(L"5.91191e-39"), results1.GetFormattedValue(2U, MemSize::Float));
+        Assert::AreEqual(std::wstring(L"6.88766e-41"), results1.GetFormattedValue(2U, MemSize::Float));
         Assert::AreEqual(std::wstring(L"8192.1875"), results1.GetFormattedValue(3U, MemSize::Float));
     }
 
diff --git a/tests/ui/viewmodels/LoginViewModel_Tests.cpp b/tests/ui/viewmodels/LoginViewModel_Tests.cpp
index 2dec1968..2816ea2b 100644
--- a/tests/ui/viewmodels/LoginViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/LoginViewModel_Tests.cpp
@@ -9,6 +9,7 @@
 #include "tests\mocks\MockServer.hh"
 #include "tests\mocks\MockSessionTracker.hh"
 #include "tests\mocks\MockUserContext.hh"
+#include "tests\mocks\MockWindowManager.hh"
 
 using namespace Microsoft::VisualStudio::CppUnitTestFramework;
 using ra::api::mocks::MockServer;
@@ -37,6 +38,7 @@ private:
         MockUserContext mockUserContext;
         MockEmulatorContext mockEmulatorContext;
         MockSessionTracker mockSessionTracker;
+        ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
     };
 
 public:
@@ -98,6 +100,7 @@ public:
         vmLogin.mockServer.HandleRequest<ra::api::Login>([](_UNUSED const ra::api::Login::Request&, ra::api::Login::Response& response)
         {
             response.Username = "User";
+            response.DisplayName = "UserDisplay";
             response.ApiToken = "ApiToken";
             response.Score = 12345;
             response.Result = ra::api::ApiResult::Success;
@@ -133,6 +136,58 @@ public:
 
         // emulator should have been notified to rebuild the RetroAchievements menu
         Assert::IsTrue(bWasMenuRebuilt);
+
+        // app title should not be updated as _RA_UpdateAppTitle was not previously called
+        Assert::AreEqual(std::wstring(L"Window"), vmLogin.mockWindowManager.Emulator.GetWindowTitle());
+    }
+
+    TEST_METHOD(TestLoginSuccessfulAppTitle)
+    {
+        LoginViewModelHarness vmLogin;
+        vmLogin.mockServer.HandleRequest<ra::api::Login>([](_UNUSED const ra::api::Login::Request&, ra::api::Login::Response& response)
+        {
+            response.Username = "User";
+            response.DisplayName = "UserDisplay";
+            response.ApiToken = "ApiToken";
+            response.Score = 12345;
+            response.Result = ra::api::ApiResult::Success;
+            return true;
+        });
+        vmLogin.mockDesktop.ExpectWindow<MessageBoxViewModel>([](MessageBoxViewModel& vmMessageBox)
+        {
+            Assert::AreEqual(std::wstring(L"Successfully logged in as User"), vmMessageBox.GetMessage());
+            Assert::AreEqual(MessageBoxViewModel::Icon::Info, vmMessageBox.GetIcon());
+            return DialogResult::OK;
+        });
+        bool bWasMenuRebuilt = false;
+        vmLogin.mockEmulatorContext.SetRebuildMenuFunction([&bWasMenuRebuilt] { bWasMenuRebuilt = true; });
+        vmLogin.mockEmulatorContext.MockClient("RATests", "0.1.2.0");
+        vmLogin.mockWindowManager.Emulator.SetAppTitleMessage("Test");
+
+        vmLogin.SetUsername(L"user");
+        vmLogin.SetPassword(L"Pa$$w0rd");
+        Assert::IsTrue(vmLogin.Login());
+        Assert::IsTrue(vmLogin.mockDesktop.WasDialogShown());
+
+        // expect case of username to be corrected by API response
+        Assert::AreEqual(std::string("User"), vmLogin.mockConfiguration.GetUsername());
+
+        // API token should not be set unless "remember me" is checked
+        Assert::AreEqual(std::string(""), vmLogin.mockConfiguration.GetApiToken());
+
+        // values should also be updated in UserContext, including API token and score
+        Assert::AreEqual(std::string("User"), vmLogin.mockUserContext.GetUsername());
+        Assert::AreEqual(std::string("ApiToken"), vmLogin.mockUserContext.GetApiToken());
+        Assert::AreEqual(12345U, vmLogin.mockUserContext.GetScore());
+
+        // session tracker should know user name
+        Assert::AreEqual(std::wstring(L"User"), vmLogin.mockSessionTracker.GetUsername());
+
+        // emulator should have been notified to rebuild the RetroAchievements menu
+        Assert::IsTrue(bWasMenuRebuilt);
+
+        // app title should be updated as _RA_UpdateAppTitle was previously called
+        Assert::AreEqual(std::wstring(L"RATests - 0.1 - Test - UserDisplay []"), vmLogin.mockWindowManager.Emulator.GetWindowTitle());
     }
 
     TEST_METHOD(TestLoginInvalidPassword)
diff --git a/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp b/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
index d3cabc6b..deee691b 100644
--- a/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
@@ -84,7 +84,7 @@ public:
 
         Assert::AreEqual({ 0U }, bookmarks.Bookmarks().Count());
 
-        Assert::AreEqual({ 13U }, bookmarks.Sizes().Count());
+        Assert::AreEqual({ 14U }, bookmarks.Sizes().Count());
         Assert::AreEqual((int)MemSize::EightBit, bookmarks.Sizes().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L" 8-bit"), bookmarks.Sizes().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)MemSize::SixteenBit, bookmarks.Sizes().GetItemAt(1)->GetId());
@@ -109,8 +109,10 @@ public:
         Assert::AreEqual(std::wstring(L"Float"), bookmarks.Sizes().GetItemAt(10)->GetLabel());
         Assert::AreEqual((int)MemSize::MBF32, bookmarks.Sizes().GetItemAt(11)->GetId());
         Assert::AreEqual(std::wstring(L"MBF32"), bookmarks.Sizes().GetItemAt(11)->GetLabel());
-        Assert::AreEqual((int)MemSize::Text, bookmarks.Sizes().GetItemAt(12)->GetId());
-        Assert::AreEqual(std::wstring(L"ASCII"), bookmarks.Sizes().GetItemAt(12)->GetLabel());
+        Assert::AreEqual((int)MemSize::MBF32LE, bookmarks.Sizes().GetItemAt(12)->GetId());
+        Assert::AreEqual(std::wstring(L"MBF32 LE"), bookmarks.Sizes().GetItemAt(12)->GetLabel());
+        Assert::AreEqual((int)MemSize::Text, bookmarks.Sizes().GetItemAt(13)->GetId());
+        Assert::AreEqual(std::wstring(L"ASCII"), bookmarks.Sizes().GetItemAt(13)->GetLabel());
 
         Assert::AreEqual({ 2U }, bookmarks.Formats().Count());
         Assert::AreEqual((int)MemFormat::Hex, bookmarks.Formats().GetItemAt(0)->GetId());
diff --git a/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp b/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
index 9f0044b7..9622f7a8 100644
--- a/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
@@ -155,7 +155,7 @@ public:
         Assert::AreEqual(std::wstring(L""), search.GetFilterRange());
         Assert::AreEqual(std::wstring(L""), search.GetFilterValue());
 
-        Assert::AreEqual({ 11U }, search.SearchTypes().Count());
+        Assert::AreEqual({ 12U }, search.SearchTypes().Count());
         Assert::AreEqual((int)ra::services::SearchType::FourBit, search.SearchTypes().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L"4-bit"), search.SearchTypes().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)ra::services::SearchType::EightBit, search.SearchTypes().GetItemAt(1)->GetId());
@@ -176,8 +176,10 @@ public:
         Assert::AreEqual(std::wstring(L"Float"), search.SearchTypes().GetItemAt(8)->GetLabel());
         Assert::AreEqual((int)ra::services::SearchType::MBF32, search.SearchTypes().GetItemAt(9)->GetId());
         Assert::AreEqual(std::wstring(L"MBF32"), search.SearchTypes().GetItemAt(9)->GetLabel());
-        Assert::AreEqual((int)ra::services::SearchType::AsciiText, search.SearchTypes().GetItemAt(10)->GetId());
-        Assert::AreEqual(std::wstring(L"ASCII Text"), search.SearchTypes().GetItemAt(10)->GetLabel());
+        Assert::AreEqual((int)ra::services::SearchType::MBF32LE, search.SearchTypes().GetItemAt(10)->GetId());
+        Assert::AreEqual(std::wstring(L"MBF32 LE"), search.SearchTypes().GetItemAt(10)->GetLabel());
+        Assert::AreEqual((int)ra::services::SearchType::AsciiText, search.SearchTypes().GetItemAt(11)->GetId());
+        Assert::AreEqual(std::wstring(L"ASCII Text"), search.SearchTypes().GetItemAt(11)->GetLabel());
         Assert::AreEqual(ra::services::SearchType::EightBit, search.GetSearchType());
 
         Assert::AreEqual({ 6U }, search.ComparisonTypes().Count());
diff --git a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
index 435fea79..2f513e61 100644
--- a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
@@ -454,6 +454,7 @@ public:
         ParseAndRegenerate("0xK1234=0xK2345"); // bitcount
         ParseAndRegenerate("fF1234=fF2345"); // float
         ParseAndRegenerate("fM1234=fM2345"); // mbf32
+        ParseAndRegenerate("fL1234=fL2345"); // mbf32le
     }
 
     TEST_METHOD(TestOperandTypes)
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index 9db3020a..fd947adf 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -164,7 +164,7 @@ public:
         Assert::AreEqual((int)TriggerOperandType::Inverted, vmTrigger.OperandTypes().GetItemAt(6)->GetId());
         Assert::AreEqual(std::wstring(L"Invert"), vmTrigger.OperandTypes().GetItemAt(6)->GetLabel());
 
-        Assert::AreEqual({ 20U }, vmTrigger.OperandSizes().Count());
+        Assert::AreEqual({ 21U }, vmTrigger.OperandSizes().Count());
         Assert::AreEqual((int)MemSize::Bit_0, vmTrigger.OperandSizes().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L"Bit0"), vmTrigger.OperandSizes().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)MemSize::Bit_1, vmTrigger.OperandSizes().GetItemAt(1)->GetId());
@@ -205,6 +205,8 @@ public:
         Assert::AreEqual(std::wstring(L"Float"), vmTrigger.OperandSizes().GetItemAt(18)->GetLabel());
         Assert::AreEqual((int)MemSize::MBF32, vmTrigger.OperandSizes().GetItemAt(19)->GetId());
         Assert::AreEqual(std::wstring(L"MBF32"), vmTrigger.OperandSizes().GetItemAt(19)->GetLabel());
+        Assert::AreEqual((int)MemSize::MBF32LE, vmTrigger.OperandSizes().GetItemAt(20)->GetId());
+        Assert::AreEqual(std::wstring(L"MBF32 LE"), vmTrigger.OperandSizes().GetItemAt(20)->GetLabel());
 
         Assert::AreEqual({ 10U }, vmTrigger.OperatorTypes().Count());
         Assert::AreEqual((int)TriggerOperatorType::Equals, vmTrigger.OperatorTypes().GetItemAt(0)->GetId());