Repository: RetroAchievements/RAIntegration
Pull request #860 (2022-01-22 05:33:37)

prevent crash viewing tooltip for address chain when in invalid state
When the trigger cannot be parsed (having a condition without a flag in the value definition), attempting to evaluate the indirect tooltip was throwing an assertion exception as the unparseable trigger could not be used to evaluate the AddAddress chain.

When the trigger is unparseable, an internal representation for the currently visible trigger is used. This PR modifies the tooltip code to leverage the internal representation when it is available.


Patch:
diff --git a/RA_Integration.sln b/RA_Integration.sln
index 1280a162..beb79f1c 100644
--- a/RA_Integration.sln
+++ b/RA_Integration.sln
@@ -1,7 +1,7 @@
 ﻿
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26403.7
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32106.194
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RA_Integration", "src\RA_Integration.vcxproj", "{825CB292-F069-4B27-82CF-3417746ACDF3}"
 EndProject
@@ -28,6 +28,8 @@ Global
 		Release|x64 = Release|x64
 		ReleaseWithDebug|Win32 = ReleaseWithDebug|Win32
 		ReleaseWithDebug|x64 = ReleaseWithDebug|x64
+		Release-xp|Win32 = Release-xp|Win32
+		Release-xp|x64 = Release-xp|x64
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Analysis|Win32.ActiveCfg = Analysis|Win32
@@ -46,6 +48,10 @@ Global
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.ReleaseWithDebug|Win32.Build.0 = ReleaseWithDebug|Win32
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.ReleaseWithDebug|x64.ActiveCfg = ReleaseWithDebug|x64
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.ReleaseWithDebug|x64.Build.0 = ReleaseWithDebug|x64
+		{825CB292-F069-4B27-82CF-3417746ACDF3}.Release-xp|Win32.ActiveCfg = Release-xp|Win32
+		{825CB292-F069-4B27-82CF-3417746ACDF3}.Release-xp|Win32.Build.0 = Release-xp|Win32
+		{825CB292-F069-4B27-82CF-3417746ACDF3}.Release-xp|x64.ActiveCfg = Release-xp|x64
+		{825CB292-F069-4B27-82CF-3417746ACDF3}.Release-xp|x64.Build.0 = Release-xp|x64
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|Win32.ActiveCfg = Analysis|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|Win32.Build.0 = Analysis|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|x64.ActiveCfg = Analysis|x64
@@ -62,6 +68,10 @@ Global
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.ReleaseWithDebug|Win32.Build.0 = Release|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.ReleaseWithDebug|x64.ActiveCfg = Release|x64
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.ReleaseWithDebug|x64.Build.0 = Release|x64
+		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Release-xp|Win32.ActiveCfg = Release-xp|Win32
+		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Release-xp|Win32.Build.0 = Release-xp|Win32
+		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Release-xp|x64.ActiveCfg = Release-xp|x64
+		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Release-xp|x64.Build.0 = Release-xp|x64
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|Win32.ActiveCfg = Release|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|Win32.Build.0 = Release|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|x64.ActiveCfg = Release|x64
@@ -78,6 +88,10 @@ Global
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.ReleaseWithDebug|Win32.Build.0 = Release|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.ReleaseWithDebug|x64.ActiveCfg = Release|x64
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.ReleaseWithDebug|x64.Build.0 = Release|x64
+		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Release-xp|Win32.ActiveCfg = Release|Win32
+		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Release-xp|Win32.Build.0 = Release|Win32
+		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Release-xp|x64.ActiveCfg = Release|x64
+		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Release-xp|x64.Build.0 = Release|x64
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Analysis|Win32.ActiveCfg = Release|Win32
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Analysis|x64.ActiveCfg = Release|x64
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Analysis|x64.Build.0 = Release|x64
@@ -93,6 +107,10 @@ Global
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.ReleaseWithDebug|Win32.Build.0 = Release|Win32
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.ReleaseWithDebug|x64.ActiveCfg = Release|x64
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.ReleaseWithDebug|x64.Build.0 = Release|x64
+		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Release-xp|Win32.ActiveCfg = Release-xp|Win32
+		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Release-xp|Win32.Build.0 = Release-xp|Win32
+		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Release-xp|x64.ActiveCfg = Release-xp|x64
+		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Release-xp|x64.Build.0 = Release-xp|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/Exports.cpp b/src/Exports.cpp
index 2136d314..349b3d38 100644
--- a/src/Exports.cpp
+++ b/src/Exports.cpp
@@ -457,7 +457,7 @@ static void ProcessAchievements()
                     {
                         ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\lb.wav");
                         auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
-                        const int nPopup = pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardStarted,
+                        pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardStarted,
                             L"Leaderboard attempt started", pLeaderboard->GetName(), pLeaderboard->GetDescription());
                     }
 
@@ -501,7 +501,7 @@ static void ProcessAchievements()
                         ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\lbcancel.wav");
 
                         auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
-                        const int nPopup = pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardCanceled,
+                        pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardCanceled,
                             L"Leaderboard attempt failed", pLeaderboard->GetName(), pLeaderboard->GetDescription());
                     }
 
diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj
index 4bf578a8..76c3cd4e 100644
--- a/src/RA_Integration.vcxproj
+++ b/src/RA_Integration.vcxproj
@@ -17,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|Win32">
+      <Configuration>Release-xp</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|x64">
+      <Configuration>Release-xp</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="ReleaseWithDebug|Win32">
       <Configuration>ReleaseWithDebug</Configuration>
       <Platform>Win32</Platform>
@@ -63,7 +71,9 @@
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Analysis|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Analysis|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|x64'">Create</PrecompiledHeader>
     </ClCompile>
@@ -354,6 +364,7 @@
     <ResourceCompile Include="RA_Version.rc">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">_DEBUG;_WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_USING_V110_SDK71_;_WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">_USING_V110_SDK71_;_WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
   </ItemGroup>
   <ItemGroup>
@@ -375,12 +386,24 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
@@ -388,24 +411,24 @@
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Analysis|Win32'" Label="Configuration">
@@ -425,10 +448,18 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="base.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="base.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="base.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="base.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="base.props" />
@@ -493,12 +524,24 @@
     <LinkIncremental>false</LinkIncremental>
     <TargetName>RA_Integration</TargetName>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
+    <LinkIncremental>false</LinkIncremental>
+    <TargetName>RA_Integration</TargetName>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <TargetName>RA_Integration</TargetName>
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <TargetName>RA_Integration</TargetName>
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'">
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
@@ -523,10 +566,9 @@
       <ShowIncludes>false</ShowIncludes>
       <ConformanceMode>true</ConformanceMode>
       <EnablePREfast>false</EnablePREfast>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
@@ -548,10 +590,9 @@
       <ShowIncludes>false</ShowIncludes>
       <ConformanceMode>true</ConformanceMode>
       <EnablePREfast>false</EnablePREfast>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
@@ -576,7 +617,7 @@
       <TreatWarningAsError>true</TreatWarningAsError>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
@@ -604,7 +645,7 @@
       <TreatWarningAsError>true</TreatWarningAsError>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <GenerateDebugInformation>false</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
@@ -629,10 +670,28 @@
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <ConformanceMode>true</ConformanceMode>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <ClCompile>
+      <Optimization>Full</Optimization>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <WholeProgramOptimization>true</WholeProgramOptimization>
+      <PreprocessorDefinitions>NDEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>false</GenerateDebugInformation>
@@ -649,10 +708,28 @@
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <ConformanceMode>true</ConformanceMode>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <ClCompile>
+      <Optimization>Full</Optimization>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <WholeProgramOptimization>true</WholeProgramOptimization>
+      <PreprocessorDefinitions>NDEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>false</GenerateDebugInformation>
@@ -673,7 +750,7 @@
       <LinkTimeCodeGeneration>UseFastLinkTimeCodeGeneration</LinkTimeCodeGeneration>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
@@ -694,7 +771,7 @@
       <LinkTimeCodeGeneration>UseFastLinkTimeCodeGeneration</LinkTimeCodeGeneration>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc
index 21d80025..cb18360a 100644
--- a/src/RA_Shared.rc
+++ b/src/RA_Shared.rc
@@ -7,7 +7,8 @@
 //
 // Generated from the TEXTINCLUDE 2 resource.
 //
-#include "afxres.h"
+#include "winresrc.h"
+#define IDC_STATIC -1
 
 /////////////////////////////////////////////////////////////////////////////
 #undef APSTUDIO_READONLY_SYMBOLS
@@ -550,7 +551,8 @@ END
 
 2 TEXTINCLUDE 
 BEGIN
-    "#include ""afxres.h""\r\n"
+    "#include ""winresrc.h""\r\n"
+    "#define IDC_STATIC -1"\r\n
     "\0"
 END
 
diff --git a/src/data/context/EmulatorContext.cpp b/src/data/context/EmulatorContext.cpp
index 89b15e32..39875dce 100644
--- a/src/data/context/EmulatorContext.cpp
+++ b/src/data/context/EmulatorContext.cpp
@@ -239,12 +239,14 @@ bool EmulatorContext::ValidateClientVersion(bool& bHardcore)
             sMinimumVersion = response.MinimumVersion;
             nMinimumVersion = ParseVersion(response.MinimumVersion.c_str());
 
+#ifndef RA_UTEST
             const unsigned long long nServerVersion = ParseVersion(m_sLatestVersion.c_str());
             const unsigned long long nLocalVersion = ParseVersion(m_sVersion.c_str());
             RA_LOG_INFO("Client %s date: server %s, current %s",
                    (nLocalVersion >= nServerVersion) ? "up to" : "out of",
                    m_sLatestVersion,
                    m_sVersion);
+#endif
         }
 
         if (m_nEmulatorId == EmulatorID::RA_Gens)
@@ -607,7 +609,6 @@ bool EmulatorContext::HasInvalidRegions() const noexcept
 
 bool EmulatorContext::IsValidAddress(ra::ByteAddress nAddress) const noexcept
 {
-    const ra::ByteAddress nOriginalAddress = nAddress;
     for (const auto& pBlock : m_vMemoryBlocks)
     {
         if (nAddress < pBlock.size)
diff --git a/src/data/context/GameAssets.cpp b/src/data/context/GameAssets.cpp
index de67fbf9..3268cf30 100644
--- a/src/data/context/GameAssets.cpp
+++ b/src/data/context/GameAssets.cpp
@@ -126,7 +126,7 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
         BeginUpdate();
         for (auto* pAsset : vAssetsToReload)
         {
-            if (pAsset->GetCategory() != ra::data::models::AssetCategory::Local)
+            if (pAsset != nullptr && pAsset->GetCategory() != ra::data::models::AssetCategory::Local)
                 pAsset->RestoreServerCheckpoint();
         }
         EndUpdate();
@@ -202,7 +202,7 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
             for (auto pIter = vRemainingAssetsToReload.begin(); pIter != vRemainingAssetsToReload.end(); ++pIter)
             {
                 auto* pAssetToReset = *pIter;
-                if (pAssetToReset->GetID() == nId && pAssetToReset->GetType() == nType)
+                if (pAssetToReset != nullptr && pAssetToReset->GetID() == nId && pAssetToReset->GetType() == nType)
                 {
                     pAsset = pAssetToReset;
                     vRemainingAssetsToReload.erase(pIter);
@@ -261,12 +261,18 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
 
     // assign IDs for any assets where one was not available
     for (auto* pAsset : vUnnumberedAssets)
-        pAsset->SetID(m_nNextLocalId++);
+    {
+        if (pAsset != nullptr)
+            pAsset->SetID(m_nNextLocalId++);
+    }
 
     // any items still in the source list no longer exist in the file. if the item is on the server,
     // just reset to the server state. otherwise, delete the item.
     for (auto* pAsset : vRemainingAssetsToReload)
     {
+        if (pAsset == nullptr)
+            continue;
+
         if (pAsset->GetCategory() == ra::data::models::AssetCategory::Local)
         {
             for (gsl::index nIndex = gsl::narrow_cast<gsl::index>(Count()) - 1; nIndex >= 0; --nIndex)
diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp
index f2978a4a..799a6eab 100644
--- a/src/data/context/GameContext.cpp
+++ b/src/data/context/GameContext.cpp
@@ -105,18 +105,27 @@ void GameContext::LoadGame(unsigned int nGameId, Mode nMode)
     }
 
     const auto& pConsoleContext = ra::services::ServiceLocator::Get<ra::data::context::ConsoleContext>();
-    if (ra::itoe<ConsoleID>(response.ConsoleId) != pConsoleContext.Id())
+    const auto nServerConsoleId = ra::itoe<ConsoleID>(response.ConsoleId);
+    if (nServerConsoleId != pConsoleContext.Id())
     {
-        m_vAssets.EndUpdate();
-        m_nGameId = 0;
+        if (nServerConsoleId == ConsoleID::GB && pConsoleContext.Id() == ConsoleID::GBC)
+        {
+            // GB memory map is just a subset of GBC, and GBC runs GB games, so allow loading a
+            // GB game in GBC context.
+        }
+        else
+        {
+            m_vAssets.EndUpdate();
+            m_nGameId = 0;
 
-        ra::ui::viewmodels::MessageBoxViewModel::ShowErrorMessage(L"Identified game does not match expected console.",
-            ra::StringPrintf(L"The game being loaded is associated to the %s console, but the emulator has initialized "
-                "the %s console. This is not allowed as the memory maps may not be compatible between consoles.",
-                rc_console_name(response.ConsoleId), pConsoleContext.Name()));
+            ra::ui::viewmodels::MessageBoxViewModel::ShowErrorMessage(L"Identified game does not match expected console.",
+                ra::StringPrintf(L"The game being loaded is associated to the %s console, but the emulator has initialized "
+                    "the %s console. This is not allowed as the memory maps may not be compatible between consoles.",
+                    rc_console_name(response.ConsoleId), pConsoleContext.Name()));
 
-        EndLoad();
-        return;
+            EndLoad();
+            return;
+        }
     }
 
     // start fetching the code notes
diff --git a/src/data/models/AchievementModel.cpp b/src/data/models/AchievementModel.cpp
index 40f85e72..5985b83f 100644
--- a/src/data/models/AchievementModel.cpp
+++ b/src/data/models/AchievementModel.cpp
@@ -251,6 +251,7 @@ bool AchievementModel::Deserialize(ra::Tokenizer& pTokenizer)
         // unquoted trigger requires special parsing because flags also use colons
         const char* pTrigger = pTokenizer.GetPointer(pTokenizer.CurrentPosition());
         const char* pScan = pTrigger;
+        Expects(pScan != nullptr);
         while (*pScan && (*pScan != ':' || strchr("ABCMNOPRTabcmnoprt", pScan[-1]) != nullptr))
             pScan++;
 
diff --git a/src/pch.h b/src/pch.h
index 02ed11f3..7767b580 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -16,7 +16,9 @@
 #include "windows_nodefines.h"
 #include <ShlObj.h> // CommCtrl
 #include <WindowsX.h>
-#include <atlbase.h> // atldef.h (Windows.h), atlcore.h (tchar.h), Shlwapi.h
+#include <Windows.h>
+#include <tchar.h>
+#include <Shlwapi.h>
 #include <direct.h>
 #include <io.h>
 #include <wincodec.h>
diff --git a/src/services/AchievementRuntime.cpp b/src/services/AchievementRuntime.cpp
index 977df9f0..0a9dc706 100644
--- a/src/services/AchievementRuntime.cpp
+++ b/src/services/AchievementRuntime.cpp
@@ -794,6 +794,7 @@ bool AchievementRuntime::LoadProgressFromFile(const char* sLoadStateFilename)
     for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()); ++nIndex)
     {
         auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
+        Expects(pAsset != nullptr);
         switch (pAsset->GetType())
         {
             case ra::data::models::AssetType::Achievement:
diff --git a/src/services/SearchResults.cpp b/src/services/SearchResults.cpp
index 88874e92..e1183679 100644
--- a/src/services/SearchResults.cpp
+++ b/src/services/SearchResults.cpp
@@ -1709,7 +1709,6 @@ bool SearchResults::GetBytes(ra::ByteAddress nAddress, unsigned char* pBuffer, s
 {
     if (m_pImpl != nullptr)
     {
-        const unsigned int nPadding = m_pImpl->GetPadding();
         for (auto& block : m_vBlocks)
         {
             if (block.GetFirstAddress() > nAddress)
diff --git a/src/ui/drawing/gdi/ImageRepository.cpp b/src/ui/drawing/gdi/ImageRepository.cpp
index 6604cb3f..f6c108b3 100644
--- a/src/ui/drawing/gdi/ImageRepository.cpp
+++ b/src/ui/drawing/gdi/ImageRepository.cpp
@@ -21,7 +21,7 @@ namespace gdi {
 inline constexpr const char* DefaultBadge{ "00000" };
 inline constexpr const char* DefaultUserPic{ "_User" };
 
-static CComPtr<IWICImagingFactory> g_pIWICFactory;
+static IWICImagingFactory* g_pIWICFactory = nullptr;
 
 bool ImageRepository::Initialize()
 {
@@ -73,7 +73,8 @@ ImageRepository::~ImageRepository() noexcept
         DeleteBitmap(userPic.second.m_hBitmap);
     m_mUserPics.clear();
 
-    g_pIWICFactory.Release();
+    if (g_pIWICFactory != nullptr)
+        g_pIWICFactory->Release();
 
     if (m_bShutdownCOM)
         CoUninitialize();
@@ -264,12 +265,13 @@ HBITMAP ImageRepository::GetDefaultImage(ImageType nType)
     }
 }
 
+GSL_SUPPRESS_F23
 static HRESULT ConvertBitmapSource(_In_ RECT rcDest, _In_ IWICBitmapSource* pOriginalBitmapSource, _Inout_ IWICBitmapSource*& pToRenderBitmapSource)
 {
     pToRenderBitmapSource = nullptr;
 
     // Create a BitmapScaler
-    CComPtr<IWICBitmapScaler> pScaler;
+    IWICBitmapScaler* pScaler = nullptr;
     auto hr = g_pIWICFactory->CreateBitmapScaler(&pScaler);
 
     // Initialize the bitmap scaler from the original bitmap map bits
@@ -284,29 +286,34 @@ static HRESULT ConvertBitmapSource(_In_ RECT rcDest, _In_ IWICBitmapSource* pOri
     // Convert the bitmap into 32bppBGR, a convenient pixel format for GDI rendering 
     if (SUCCEEDED(hr))
     {
-        CComPtr<IWICFormatConverter> pConverter;
+        IWICFormatConverter* pConverter = nullptr;
         hr = g_pIWICFactory->CreateFormatConverter(&pConverter);
 
         // Format convert to 32bppBGR
         if (SUCCEEDED(hr))
         {
-            hr = pConverter->Initialize(static_cast<IWICBitmapSource*>(pScaler), // Input bitmap to convert
+            hr = pConverter->Initialize(pScaler,            // Input bitmap to convert
                 GUID_WICPixelFormat32bppBGR,                // &GUID_WICPixelFormat32bppBGR,
                 WICBitmapDitherTypeNone,                    // Specified dither pattern
                 nullptr,                                    // Specify a particular palette 
-                0.f,                                        // Alpha threshold
+                0.0f,                                       // Alpha threshold
                 WICBitmapPaletteTypeCustom);                // Palette translation type
 
             // Store the converted bitmap as ppToRenderBitmapSource 
             if (SUCCEEDED(hr))
+            {
                 GSL_SUPPRESS_TYPE1
                 pConverter->QueryInterface(IID_IWICBitmapSource, reinterpret_cast<void**>(&pToRenderBitmapSource));
+            }
         }
 
-        pConverter.Release();
+        if (pConverter != nullptr)
+            pConverter->Release();
     }
 
-    pScaler.Release();
+    if (pScaler != nullptr)
+        pScaler->Release();
+
     return hr;
 }
 
@@ -389,13 +396,14 @@ static HRESULT CreateDIBFromBitmapSource(_In_ IWICBitmapSource* pToRenderBitmapS
     return hr;
 }
 
+GSL_SUPPRESS_F23
 HBITMAP ImageRepository::LoadLocalPNG(const std::wstring& sFilename, unsigned int nWidth, unsigned int nHeight)
 {
     if (g_pIWICFactory == nullptr)
         return nullptr;
 
     // Decode the source image to IWICBitmapSource
-    CComPtr<IWICBitmapDecoder> pDecoder;
+    IWICBitmapDecoder* pDecoder = nullptr;
     HRESULT hr = g_pIWICFactory->CreateDecoderFromFilename(ra::Widen(sFilename).c_str(), // Image to be decoded
                                                            nullptr,      // Do not prefer a particular vendor
                                                            GENERIC_READ, // Desired read access to the file
@@ -403,12 +411,12 @@ HBITMAP ImageRepository::LoadLocalPNG(const std::wstring& sFilename, unsigned in
                                                            &pDecoder);                     // Pointer to the decoder
 
     // Retrieve the first frame of the image from the decoder
-    CComPtr<IWICBitmapFrameDecode> pFrame;
+    IWICBitmapFrameDecode* pFrame = nullptr;
     if (SUCCEEDED(hr))
         hr = pDecoder->GetFrame(0, &pFrame);
 
     // Retrieve IWICBitmapSource from the frame
-    CComPtr<IWICBitmapSource> pOriginalBitmapSource;
+    IWICBitmapSource* pOriginalBitmapSource = nullptr;
     if (SUCCEEDED(hr))
     {
         GSL_SUPPRESS_TYPE1
@@ -421,7 +429,7 @@ HBITMAP ImageRepository::LoadLocalPNG(const std::wstring& sFilename, unsigned in
     }
 
     // Scale the original IWICBitmapSource to the client rect size and convert the pixel format
-    CComPtr<IWICBitmapSource> pToRenderBitmapSource;
+    IWICBitmapSource* pToRenderBitmapSource = nullptr;
     if (SUCCEEDED(hr))
         hr = ConvertBitmapSource({0, 0, to_signed(nWidth), to_signed(nHeight)}, pOriginalBitmapSource,
                                  *&pToRenderBitmapSource);
@@ -431,10 +439,17 @@ HBITMAP ImageRepository::LoadLocalPNG(const std::wstring& sFilename, unsigned in
     if (SUCCEEDED(hr))
         hr = CreateDIBFromBitmapSource(pToRenderBitmapSource, hBitmap);
 
-    pToRenderBitmapSource.Release();
-    pOriginalBitmapSource.Release();
-    pFrame.Release();
-    pDecoder.Release();
+    if (pToRenderBitmapSource != nullptr)
+        pToRenderBitmapSource->Release();
+
+    if (pOriginalBitmapSource != nullptr)
+        pOriginalBitmapSource->Release();
+
+    if (pFrame != nullptr)
+        pFrame->Release();
+
+    if (pDecoder != nullptr)
+        pDecoder->Release();
 
     return hBitmap;
 }
@@ -583,6 +598,7 @@ bool ImageRepository::HasReferencedImageChanged(ImageReference& pImage) const
     return (pImage.m_nData != hBitmapBefore);
 }
 
+GSL_SUPPRESS_F23
 bool GDISurfaceFactory::SaveImage(const ISurface& pSurface, const std::wstring& sPath) const
 {
     if (g_pIWICFactory == nullptr)
@@ -596,11 +612,11 @@ bool GDISurfaceFactory::SaveImage(const ISurface& pSurface, const std::wstring&
         return false;
 
     // create a PNG encoder
-    CComPtr<IWICBitmapEncoder> pEncoder;
+    IWICBitmapEncoder* pEncoder = nullptr;
     HRESULT hr = g_pIWICFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &pEncoder);
 
     // open the file stream
-    CComPtr<IWICStream> pStream;
+    IWICStream* pStream = nullptr;
     if (SUCCEEDED(hr))
         hr = g_pIWICFactory->CreateStream(&pStream);
 
@@ -611,7 +627,7 @@ bool GDISurfaceFactory::SaveImage(const ISurface& pSurface, const std::wstring&
         hr = pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
 
     // convert the image to a WICBitmap
-    CComPtr<IWICBitmapFrameEncode> pFrameEncode;
+    IWICBitmapFrameEncode* pFrameEncode = nullptr;
     if (SUCCEEDED(hr))
         hr = pEncoder->CreateNewFrame(&pFrameEncode, nullptr);
 
@@ -626,7 +642,7 @@ bool GDISurfaceFactory::SaveImage(const ISurface& pSurface, const std::wstring&
     if (SUCCEEDED(hr))
         hr = pFrameEncode->SetPixelFormat(&pixelFormat);
 
-    CComPtr<IWICBitmap> pWicBitmap;
+    IWICBitmap* pWicBitmap = nullptr;
     if (SUCCEEDED(hr))
         hr = g_pIWICFactory->CreateBitmapFromHBITMAP(pGDIBitmapSurface->GetHBitmap(), nullptr, WICBitmapIgnoreAlpha, &pWicBitmap);
 
@@ -643,6 +659,18 @@ bool GDISurfaceFactory::SaveImage(const ISurface& pSurface, const std::wstring&
     if (!SUCCEEDED(hr))
         RA_LOG_WARN("Error %08x saving %s", hr, sPath);
 
+    if (pWicBitmap != nullptr)
+        pWicBitmap->Release();
+
+    if (pFrameEncode != nullptr)
+        pFrameEncode->Release();
+
+    if (pStream != nullptr)
+        pStream->Release();
+
+    if (pEncoder != nullptr)
+        pEncoder->Release();
+
     return SUCCEEDED(hr);
 }
 
diff --git a/src/ui/viewmodels/AssetListViewModel.cpp b/src/ui/viewmodels/AssetListViewModel.cpp
index 0fab0a8a..8180cf44 100644
--- a/src/ui/viewmodels/AssetListViewModel.cpp
+++ b/src/ui/viewmodels/AssetListViewModel.cpp
@@ -134,6 +134,7 @@ void AssetListViewModel::OnDataModelIntValueChanged(gsl::index nIndex, const Int
     {
         const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
         const auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
+        Expects(pAsset != nullptr);
         const auto* pAchievement = dynamic_cast<const ra::data::models::AchievementModel*>(pAsset);
         if (pAchievement != nullptr && GetFilteredAssetIndex(*pAsset) >= 0)
         {
@@ -155,6 +156,7 @@ void AssetListViewModel::OnDataModelIntValueChanged(gsl::index nIndex, const Int
         {
             const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
             const auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
+            Expects(pAsset != nullptr);
             switch (pAsset->GetType())
             {
                 case ra::data::models::AssetType::Achievement:
@@ -179,7 +181,8 @@ void AssetListViewModel::OnDataModelIntValueChanged(gsl::index nIndex, const Int
             // if KeepActive is selected, set a Triggered achievement back to Waiting
             auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
             auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
-            pAsset->SetState(ra::data::models::AssetState::Waiting);
+            if (pAsset != nullptr)
+                pAsset->SetState(ra::data::models::AssetState::Waiting);
 
             // SetState is re-entrant - we don't want to do any further processing with the previous new value
             return;
@@ -501,8 +504,6 @@ bool AssetListViewModel::AddOrRemoveFilteredItem(const ra::data::models::AssetMo
     {
         if (nIndex >= 0)
         {
-            const bool bIsSelected = m_vFilteredAssets.GetItemValue(nIndex, AssetSummaryViewModel::IsSelectedProperty);
-
             m_vFilteredAssets.RemoveAt(nIndex);
             return true;
         }
@@ -617,7 +618,6 @@ void AssetListViewModel::DoUpdateButtons()
     bool bHasUnofficial = false;
     bool bHasCore = false;
     bool bHasLocal = false;
-    const bool bHardcore = _RA_HardcoreModeIsActive();
 
     const bool bGameLoaded = (GetGameId() != 0);
     if (!bGameLoaded)
@@ -975,6 +975,9 @@ void AssetListViewModel::SaveSelected()
             std::string sMessage;
             for (const auto* pAsset : vSelectedAssets)
             {
+                if (pAsset == nullptr)
+                    continue;
+
                 if (!sMessage.empty())
                     sMessage.push_back(',');
                 sMessage.append(std::to_string(pAsset->GetID()));
@@ -1028,7 +1031,10 @@ void AssetListViewModel::SaveSelected()
             return;
 
         for (auto* pAsset : vSelectedAssets)
-            pAsset->SetCategory(ra::data::models::AssetCategory::Core);
+        {
+            if (pAsset != nullptr)
+                pAsset->SetCategory(ra::data::models::AssetCategory::Core);
+        }
 
         RA_LOG_INFO("Promoting %u items", vSelectedAssets.size());
         Publish(vSelectedAssets);
@@ -1047,7 +1053,7 @@ void AssetListViewModel::SaveSelected()
 
         for (const auto* pAsset : vSelectedAssets)
         {
-            if (pAsset->GetType() == ra::data::models::AssetType::Leaderboard)
+            if (pAsset != nullptr && pAsset->GetType() == ra::data::models::AssetType::Leaderboard)
             {
                 ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Leaderboards cannot be demoted.");
                 return;
@@ -1063,7 +1069,10 @@ void AssetListViewModel::SaveSelected()
             return;
 
         for (auto* pAsset : vSelectedAssets)
-            pAsset->SetCategory(ra::data::models::AssetCategory::Unofficial);
+        {
+            if (pAsset != nullptr)
+                pAsset->SetCategory(ra::data::models::AssetCategory::Unofficial);
+        }
 
         RA_LOG_INFO("Demoting %u items", vSelectedAssets.size());
         Publish(vSelectedAssets);
@@ -1336,12 +1345,15 @@ void AssetListViewModel::ResetSelected()
         // reset selection, remove "new" items and get the AssetViewModel for the others
         for (auto* pItem : vSelectedAssets)
         {
+            if (pItem == nullptr)
+                continue;
+
             const auto nType = pItem->GetType();
             const auto nId = ra::to_unsigned(pItem->GetId());
             for (gsl::index nIndex = gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()) - 1; nIndex >= 0; --nIndex)
             {
                 auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
-                if (pAsset->GetID() == nId && pAsset->GetType() == nType)
+                if (pAsset != nullptr && pAsset->GetID() == nId && pAsset->GetType() == nType)
                 {
                     if (pAsset->GetState() == ra::data::models::AssetState::Primed)
                     {
@@ -1371,6 +1383,9 @@ void AssetListViewModel::ResetSelected()
             std::string sMessage;
             for (const auto* pAsset : vAssetsToReset)
             {
+                if (pAsset == nullptr)
+                    continue;
+
                 if (!sMessage.empty())
                     sMessage.push_back(',');
                 sMessage.append(std::to_string(pAsset->GetID()));
@@ -1482,6 +1497,9 @@ void AssetListViewModel::RevertSelected()
     std::string sMessage;
     for (const auto* pAsset : vAssetsToReset)
     {
+        if (pAsset == nullptr)
+            continue;
+
         if (!sMessage.empty())
             sMessage.push_back(',');
         sMessage.append(std::to_string(pAsset->GetID()));
@@ -1635,6 +1653,9 @@ void AssetListViewModel::CloneSelected()
         std::string sMessage;
         for (const auto* pAsset : vSelectedAssets)
         {
+            if (pAsset == nullptr)
+                continue;
+
             if (!sMessage.empty())
                 sMessage.push_back(',');
             sMessage.append(std::to_string(pAsset->GetID()));
diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
index cfe5d707..d58771d4 100644
--- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
+++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp
@@ -340,7 +340,10 @@ void MemoryBookmarksViewModel::LoadBookmarks(ra::services::TextReader& sBookmark
             {
                 auto* vmBookmark = m_vBookmarks.GetItemAt(nIndex);
                 if (vmBookmark == nullptr)
+                {
                     vmBookmark = &m_vBookmarks.Add();
+                    Ensures(vmBookmark != nullptr);
+                }
                 ++nIndex;
 
                 vmBookmark->BeginInitialization();
diff --git a/src/ui/viewmodels/MemorySearchViewModel.cpp b/src/ui/viewmodels/MemorySearchViewModel.cpp
index ffd05f52..8bbd66aa 100644
--- a/src/ui/viewmodels/MemorySearchViewModel.cpp
+++ b/src/ui/viewmodels/MemorySearchViewModel.cpp
@@ -718,7 +718,10 @@ void MemorySearchViewModel::UpdateResults()
 
         auto* pRow = m_vResults.GetItemAt(nRow);
         if (pRow == nullptr)
+        {
             pRow = &m_vResults.Add();
+            Ensures(pRow != nullptr);
+        }
 
         pRow->nAddress = pResult.nAddress;
 
diff --git a/src/ui/viewmodels/OverlayLeaderboardsPageViewModel.cpp b/src/ui/viewmodels/OverlayLeaderboardsPageViewModel.cpp
index f9fd1abc..8ead492a 100644
--- a/src/ui/viewmodels/OverlayLeaderboardsPageViewModel.cpp
+++ b/src/ui/viewmodels/OverlayLeaderboardsPageViewModel.cpp
@@ -80,6 +80,9 @@ void OverlayLeaderboardsPageViewModel::Refresh()
 
         for (const auto* vmLeaderboard : vActiveLeaderboards)
         {
+            if (vmLeaderboard == nullptr)
+                continue;
+
             auto& pvmLeaderboard = GetNextItem(&nIndex);
             SetLeaderboard(pvmLeaderboard, *vmLeaderboard);
         }
@@ -97,6 +100,9 @@ void OverlayLeaderboardsPageViewModel::Refresh()
 
         for (const auto* vmLeaderboard : vLocalLeaderboards)
         {
+            if (vmLeaderboard == nullptr)
+                continue;
+
             auto& pvmLeaderboard = GetNextItem(&nIndex);
             SetLeaderboard(pvmLeaderboard, *vmLeaderboard);
         }
@@ -114,6 +120,9 @@ void OverlayLeaderboardsPageViewModel::Refresh()
 
         for (const auto* vmLeaderboard : vCoreLeaderboards)
         {
+            if (vmLeaderboard == nullptr)
+                continue;
+
             auto& pvmLeaderboard = GetNextItem(&nIndex);
             SetLeaderboard(pvmLeaderboard, *vmLeaderboard);
         }
@@ -131,6 +140,9 @@ void OverlayLeaderboardsPageViewModel::Refresh()
 
         for (const auto* vmLeaderboard : vUnsupportedLeaderboards)
         {
+            if (vmLeaderboard == nullptr)
+                continue;
+
             auto& pvmLeaderboard = GetNextItem(&nIndex);
             SetLeaderboard(pvmLeaderboard, *vmLeaderboard);
         }
diff --git a/src/ui/viewmodels/ScoreboardViewModel.cpp b/src/ui/viewmodels/ScoreboardViewModel.cpp
index 6c114ce6..96c52fc8 100644
--- a/src/ui/viewmodels/ScoreboardViewModel.cpp
+++ b/src/ui/viewmodels/ScoreboardViewModel.cpp
@@ -37,7 +37,6 @@ void ScoreboardViewModel::BeginAnimation()
     m_fAnimationProgress = 0.0;
 
     const auto& pTheme = ra::services::ServiceLocator::Get<ra::ui::OverlayTheme>();
-    const auto nHeight = CalculateScoreboardHeight(pTheme);
     const auto nWidth = CalculateScoreboardWidth(pTheme);
 
     // bottom margin 10px
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.cpp b/src/ui/viewmodels/TriggerConditionViewModel.cpp
index 117b68aa..40e149f2 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.cpp
+++ b/src/ui/viewmodels/TriggerConditionViewModel.cpp
@@ -204,6 +204,10 @@ void TriggerConditionViewModel::SerializeAppendOperand(std::string& sBuffer, Tri
             sBuffer.push_back('b');
             break;
 
+        case TriggerOperandType::Inverted:
+            sBuffer.push_back('~');
+            break;
+
         default:
             assert(!"Unknown operand type");
             break;
@@ -363,6 +367,7 @@ void TriggerConditionViewModel::SetOperand(const IntModelProperty& pTypeProperty
         case TriggerOperandType::Delta:
         case TriggerOperandType::Prior:
         case TriggerOperandType::BCD:
+        case TriggerOperandType::Inverted:
         {
             const auto nSize = ra::data::models::TriggerValidation::MapRcheevosMemSize(operand.size);
             SetValue(pSizeProperty, ra::etoi(nSize));
@@ -587,11 +592,19 @@ unsigned int TriggerConditionViewModel::GetIndirectAddress(unsigned int nAddress
     if (pGroup == nullptr)
         return nAddress;
 
+    rc_condition_t* pCondition = nullptr;
+
     const auto* pTrigger = pTriggerViewModel->GetTriggerFromString();
     if (pTrigger != nullptr)
     {
         // if the trigger is managed by the viewmodel (not the runtime) then we need to update the memrefs
         rc_update_memref_values(pTrigger->memrefs, rc_peek_callback, nullptr);
+        pCondition = pTrigger->requirement->conditions;
+    }
+    else
+    {
+        Expects(pGroup->m_pConditionSet != nullptr);
+        pCondition = pGroup->m_pConditionSet->conditions;
     }
 
     bool bIsIndirect = false;
@@ -600,10 +613,7 @@ unsigned int TriggerConditionViewModel::GetIndirectAddress(unsigned int nAddress
     rc_typed_value_t value = {};
     oEvalState.peek = rc_peek_callback;
 
-    Expects(pGroup->m_pConditionSet != nullptr);
-
     gsl::index nConditionIndex = 0;
-    rc_condition_t* pCondition = pGroup->m_pConditionSet->conditions;
     for (; pCondition != nullptr; pCondition = pCondition->next)
     {
         auto* vmCondition = pTriggerViewModel->Conditions().GetItemAt(nConditionIndex++);
@@ -729,6 +739,24 @@ bool TriggerConditionViewModel::IsAddressType(TriggerOperandType nType) noexcept
     }
 }
 
+
+bool TriggerConditionViewModel::IsOperandTypeVisible(const ViewModelBase& vmItem, int nValue) noexcept
+{
+    const auto* vmCondition = dynamic_cast<const TriggerConditionViewModel*>(&vmItem);
+    if (vmCondition == nullptr)
+        return false;
+
+    const auto nOperandType = ra::itoe<TriggerOperandType>(nValue);
+    switch (nOperandType)
+    {
+        case TriggerOperandType::Inverted:
+            return vmCondition->IsForValue();
+
+        default:
+            return true;
+    }
+}
+
 bool TriggerConditionViewModel::IsComparisonVisible(const ViewModelBase& vmItem, int nValue)
 {
     const auto* vmCondition = dynamic_cast<const TriggerConditionViewModel*>(&vmItem);
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.hh b/src/ui/viewmodels/TriggerConditionViewModel.hh
index 6ea78338..71c2fe78 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.hh
+++ b/src/ui/viewmodels/TriggerConditionViewModel.hh
@@ -40,7 +40,8 @@ enum class TriggerOperandType : uint8_t
     Value = RC_OPERAND_CONST,           // a 32 bit unsigned integer
     Prior = RC_OPERAND_PRIOR,           // the last differing value at this address.
     BCD = RC_OPERAND_BCD,               // Address, but decoded from binary-coded-decimal
-    Float = RC_OPERAND_FP               // a 32-bit floating point value
+    Float = RC_OPERAND_FP,              // a 32-bit floating point value
+    Inverted = RC_OPERAND_INVERTED      // the bitwise compliment of the current value at the address
 };
 
 enum class TriggerOperatorType : uint8_t
@@ -136,6 +137,7 @@ public:
     bool IsModifying() const { return IsModifying(GetType()); }
 
     static bool IsComparisonVisible(const ViewModelBase& vmItem, int nValue);
+    static bool IsOperandTypeVisible(const ViewModelBase& vmItem, int nValue) noexcept;
     static std::wstring FormatValue(unsigned nValue, TriggerOperandType nType);
     static std::wstring FormatValue(float fValue, TriggerOperandType nType);
 
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 2c3968fc..4985fc3c 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -47,6 +47,7 @@ TriggerViewModel::TriggerViewModel() noexcept
     m_vOperandTypes.Add(ra::etoi(TriggerOperandType::Prior), L"Prior");
     m_vOperandTypes.Add(ra::etoi(TriggerOperandType::BCD), L"BCD");
     m_vOperandTypes.Add(ra::etoi(TriggerOperandType::Float), L"Float");
+    m_vOperandTypes.Add(ra::etoi(TriggerOperandType::Inverted), L"Invert");
 
     m_vOperandSizes.Add(ra::etoi(MemSize::Bit_0), L"Bit0");
     m_vOperandSizes.Add(ra::etoi(MemSize::Bit_1), L"Bit1");
@@ -448,6 +449,7 @@ rc_trigger_t* TriggerViewModel::ParseTrigger(const std::string& sTrigger)
             memset(pTrigger, 0, sizeof(rc_trigger_t));
             pTrigger->requirement = pValue->conditions;
             pTrigger->alternative = pValue->conditions->next;
+            pTrigger->memrefs = pValue->memrefs;
             return pTrigger;
         }
     }
@@ -494,6 +496,7 @@ void TriggerViewModel::InitializeFrom(const rc_value_t& pValue)
     memset(m_pTrigger, 0, sizeof(rc_trigger_t));
     m_pTrigger->requirement = pValue.conditions;
     m_pTrigger->alternative = (pValue.conditions) ? pValue.conditions->next : nullptr;
+    m_pTrigger->memrefs = pValue.memrefs;
     InitializeGroups(*m_pTrigger);
 }
 
@@ -923,7 +926,12 @@ void TriggerViewModel::DoFrame()
         for (; pCondition != nullptr; pCondition = pCondition->next)
         {
             auto* vmCondition = m_vConditions.GetItemAt(nConditionIndex++);
-            Expects(vmCondition != nullptr);
+            if (vmCondition == nullptr)
+            {
+                // assume the trigger is being updated on another thread and we'll
+                // resynchronize the hit counts on the next frame
+                break;
+            }
 
             vmCondition->SetCurrentHits(pCondition->current_hits);
 
diff --git a/src/ui/win32/AssetEditorDialog.cpp b/src/ui/win32/AssetEditorDialog.cpp
index 5bd04c5a..cff30aba 100644
--- a/src/ui/win32/AssetEditorDialog.cpp
+++ b/src/ui/win32/AssetEditorDialog.cpp
@@ -750,6 +750,7 @@ AssetEditorDialog::AssetEditorDialog(AssetEditorViewModel& vmAssetEditor)
         TriggerConditionViewModel::SourceTypeProperty, vmAssetEditor.Trigger().OperandTypes());
     pSourceTypeColumn->SetHeader(L"Type");
     pSourceTypeColumn->SetWidth(ra::ui::win32::bindings::GridColumnBinding::WidthType::Pixels, COLUMN_WIDTH_TYPE);
+    pSourceTypeColumn->SetVisibilityFilter(TriggerConditionViewModel::IsOperandTypeVisible);
     pSourceTypeColumn->SetReadOnly(false);
     m_bindConditions.BindColumn(2, std::move(pSourceTypeColumn));
 
@@ -779,6 +780,7 @@ AssetEditorDialog::AssetEditorDialog(AssetEditorViewModel& vmAssetEditor)
         TriggerConditionViewModel::HasTargetProperty, vmAssetEditor.Trigger().OperandTypes());
     pTargetTypeColumn->SetHeader(L"Type");
     pTargetTypeColumn->SetWidth(ra::ui::win32::bindings::GridColumnBinding::WidthType::Pixels, COLUMN_WIDTH_TYPE);
+    pTargetTypeColumn->SetVisibilityFilter(TriggerConditionViewModel::IsOperandTypeVisible);
     pTargetTypeColumn->SetReadOnly(false);
     m_bindConditions.BindColumn(6, std::move(pTargetTypeColumn));
 
diff --git a/src/ui/win32/FileDialog.cpp b/src/ui/win32/FileDialog.cpp
index baa5802c..bdd2f96c 100644
--- a/src/ui/win32/FileDialog.cpp
+++ b/src/ui/win32/FileDialog.cpp
@@ -42,15 +42,16 @@ BrowseCallbackProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ _UNUSED LPARAM lParam, _
     return 0;
 }
 
+GSL_SUPPRESS_F23
 static void ShowFolder(FileDialogViewModel& vmFileDialog, HWND hParentWnd)
 {
-    CComPtr<IFileDialog> pFileDialog;
+    IFileDialog* pFileDialog = nullptr;
     if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileDialog))))
     {
         pFileDialog->SetTitle(vmFileDialog.GetWindowTitle().c_str());
 
         const std::wstring& sInitialLocation = vmFileDialog.GetInitialDirectory();
-        CComPtr<IShellItem> pShellItem;
+        IShellItem* pShellItem = nullptr;
         if (!sInitialLocation.empty())
         {
             LPITEMIDLIST pItemIdList = nullptr;
@@ -60,7 +61,7 @@ static void ShowFolder(FileDialogViewModel& vmFileDialog, HWND hParentWnd)
                 if (SHCreateShellItem(nullptr, nullptr, pItemIdList, &pShellItem) == 0)
                 {
                     pFileDialog->SetFolder(pShellItem);
-                    pShellItem.Release();
+                    pShellItem->Release();
                 }
             }
         }
@@ -70,6 +71,8 @@ static void ShowFolder(FileDialogViewModel& vmFileDialog, HWND hParentWnd)
             pFileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS);
 
         const HRESULT hr = pFileDialog->Show(hParentWnd);
+        pFileDialog->Release();
+
         if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
         {
             vmFileDialog.SetDialogResult(ra::ui::DialogResult::Cancel);
@@ -87,7 +90,7 @@ static void ShowFolder(FileDialogViewModel& vmFileDialog, HWND hParentWnd)
                     vmFileDialog.SetDialogResult(ra::ui::DialogResult::OK);
                 }
 
-                pShellItem.Release();
+                pShellItem->Release();
             }
 
             return;
@@ -164,7 +167,7 @@ void FileDialog::Presenter::DoShowModal(ra::ui::WindowViewModelBase& oViewModel,
         if (!sDefaultExtension.empty())
         {
             const wchar_t* pStart = pPair.first.data();
-            while (*pStart)
+            while (pStart != nullptr && *pStart)
             {
                 while (*pStart && *pStart != '.')
                     ++pStart;
@@ -173,6 +176,7 @@ void FileDialog::Presenter::DoShowModal(ra::ui::WindowViewModelBase& oViewModel,
                     break;
 
                 const wchar_t* pEnd = ++pStart;
+                Expects(pEnd != nullptr);
                 while (*pEnd && *pEnd != ';')
                     ++pEnd;
 


Test patch:
diff --git a/tests/RA_Integration.Tests.vcxproj b/tests/RA_Integration.Tests.vcxproj
index a05fae75..8406125e 100644
--- a/tests/RA_Integration.Tests.vcxproj
+++ b/tests/RA_Integration.Tests.vcxproj
@@ -17,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|Win32">
+      <Configuration>Release-xp</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|x64">
+      <Configuration>Release-xp</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -48,14 +56,14 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v141_xp</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
@@ -74,6 +82,14 @@
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+    <UseOfMfc>false</UseOfMfc>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseDebugLibraries>false</UseDebugLibraries>
     <PlatformToolset>v141_xp</PlatformToolset>
@@ -82,6 +98,14 @@
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+    <UseOfMfc>false</UseOfMfc>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseDebugLibraries>false</UseDebugLibraries>
     <PlatformToolset>v141_xp</PlatformToolset>
@@ -114,10 +138,18 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="base.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="base.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="base.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="base.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LinkIncremental>true</LinkIncremental>
@@ -150,11 +182,21 @@
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Integration\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Integration\</IntDir>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Integration\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Integration\</IntDir>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Integration\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Integration\</IntDir>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Integration\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Integration\</IntDir>
+  </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
@@ -258,6 +300,27 @@
       <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
     </PreBuildEvent>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>$(RA_IncludePath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;RA_UTEST;RA_EXPORTS;NDEBUG;RA_V141_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <UseFullPaths>true</UseFullPaths>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <DisableSpecificWarnings>4201</DisableSpecificWarnings>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+    </Link>
+    <PreBuildEvent>
+      <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
+    </PreBuildEvent>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
@@ -279,6 +342,27 @@
       <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
     </PreBuildEvent>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>$(RA_IncludePath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;RA_UTEST;RA_EXPORTS;NDEBUG;RA_V141_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <UseFullPaths>true</UseFullPaths>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <DisableSpecificWarnings>4201</DisableSpecificWarnings>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+    </Link>
+    <PreBuildEvent>
+      <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
+    </PreBuildEvent>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\src\data\context\GameAssets.cpp" />
     <ClCompile Include="..\src\data\ModelProperty.cpp" />
@@ -308,7 +392,9 @@
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Analysis|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Analysis|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">Create</PrecompiledHeader>
     </ClCompile>
     <ClCompile Include="..\src\Exports.cpp" />
     <ClCompile Include="..\src\RA_Json.cpp" />
diff --git a/tests/RA_Interface.Tests.vcxproj b/tests/RA_Interface.Tests.vcxproj
index 785ad811..86d9e3b1 100644
--- a/tests/RA_Interface.Tests.vcxproj
+++ b/tests/RA_Interface.Tests.vcxproj
@@ -9,6 +9,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|Win32">
+      <Configuration>Release-xp</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release-xp|x64">
+      <Configuration>Release-xp</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -49,6 +57,14 @@
     <CharacterSet>MultiByte</CharacterSet>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+    <UseOfMfc>false</UseOfMfc>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseDebugLibraries>false</UseDebugLibraries>
@@ -57,6 +73,14 @@
     <CharacterSet>MultiByte</CharacterSet>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+    <UseOfMfc>false</UseOfMfc>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -71,9 +95,15 @@
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LinkIncremental>true</LinkIncremental>
@@ -90,11 +120,21 @@
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Interface\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Interface\</IntDir>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Interface\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Interface\</IntDir>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Interface\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Interface\</IntDir>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\tests\RA_Interface\</OutDir>
+    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\tests\RA_Interface\</IntDir>
+  </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
@@ -171,7 +211,6 @@
       <UseFullPaths>true</UseFullPaths>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <DisableSpecificWarnings>4201</DisableSpecificWarnings>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
       <PrecompiledHeader>NotUsing</PrecompiledHeader>
       <WarningLevel>Level3</WarningLevel>
       <PrecompiledHeaderFile />
@@ -188,6 +227,34 @@
       <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
     </PreBuildEvent>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|Win32'">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>$(VCInstallDir)UnitTest\include\;$(SolutionDir)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;RA_UTEST;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <UseFullPaths>true</UseFullPaths>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <DisableSpecificWarnings>4201</DisableSpecificWarnings>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeaderFile>
+      </PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>
+      </PrecompiledHeaderOutputFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>kernel32.lib;user32.lib;Winhttp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <PreBuildEvent>
+      <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
+    </PreBuildEvent>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
@@ -198,7 +265,31 @@
       <UseFullPaths>true</UseFullPaths>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <DisableSpecificWarnings>4201</DisableSpecificWarnings>
-      <AdditionalOptions>/w45045 %(AdditionalOptions)</AdditionalOptions>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeaderFile>
+      </PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>
+      </PrecompiledHeaderOutputFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>kernel32.lib;user32.lib;Winhttp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>$(VCInstallDir)UnitTest\include\;$(SolutionDir)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;RA_UTEST;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <UseFullPaths>true</UseFullPaths>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <DisableSpecificWarnings>4201</DisableSpecificWarnings>
       <PrecompiledHeader>NotUsing</PrecompiledHeader>
       <WarningLevel>Level3</WarningLevel>
       <PrecompiledHeaderFile>
@@ -235,6 +326,27 @@
       <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
     </PreBuildEvent>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-xp|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <Optimization>Disabled</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>$(VCInstallDir)UnitTest\include\;$(SolutionDir)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>RA_UTEST;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <UseFullPaths>true</UseFullPaths>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+    </Link>
+    <PreBuildEvent>
+      <Command>if not exist "$(SolutionDir)src\RA_BuildVer.h" "$(SolutionDir)RAInterface\MakeBuildVer.bat" "$(SolutionDir)src\RA_BuildVer.h" RAIntegration RA_INTEGRATION</Command>
+    </PreBuildEvent>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="RA_Interface_Tests.cpp" />
   </ItemGroup>
diff --git a/tests/RA_UnitTestHelpers.h b/tests/RA_UnitTestHelpers.h
index 3c58d155..ca5538da 100644
--- a/tests/RA_UnitTestHelpers.h
+++ b/tests/RA_UnitTestHelpers.h
@@ -25,10 +25,9 @@ std::wstring ToString<std::chrono::milliseconds>(const std::chrono::milliseconds
     return std::to_wstring(t.count()) + L"ms";
 }
 
-#ifndef RA_ANALYSIS
+#ifdef RA_V141_XP
 // this has to be defined when compiling against v141_xp, but not when compiling against v142.
-// since it's impractical to detect the selected platform toolset, just key off the fact that
-// the Analysis builds have to use the newer toolset.
+// since it's impractical to detect the selected platform toolset, use a compilation flag
 
 template<>
 std::wstring ToString<__int64>(const __int64& t)
diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp
index 2b82964a..4f6f4c06 100644
--- a/tests/data/context/GameContext_Tests.cpp
+++ b/tests/data/context/GameContext_Tests.cpp
@@ -231,6 +231,43 @@ public:
         Assert::AreEqual(std::wstring(L""), game.GameTitle());
     }
 
+    TEST_METHOD(TestLoadGameTitleConsoleMismatchGBvGBC)
+    {
+        GameContextHarness game;
+        game.mockServer.HandleRequest<ra::api::FetchGameData>([](const ra::api::FetchGameData::Request& request, ra::api::FetchGameData::Response& response)
+        {
+            Assert::AreEqual(1U, request.GameId);
+
+            response.Title = L"Game";
+            response.ConsoleId = ra::etoi(ConsoleID::GB);
+            response.ImageIcon = "9743";
+            return true;
+        });
+
+        game.mockConsoleContext.SetId(ConsoleID::GBC);
+        game.mockConsoleContext.SetName(L"GameBoy Color");
+
+        bool bDialogShown = false;
+        game.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bDialogShown](ra::ui::viewmodels::MessageBoxViewModel&)
+        {
+            bDialogShown = true;
+            return ra::ui::DialogResult::OK;
+        });
+
+        game.LoadGame(1U);
+
+        Assert::IsTrue(game.mockAudioSystem.WasAudioFilePlayed(std::wstring(L"Overlay\\info.wav")));
+
+        const auto* pPopup = game.mockOverlayManager.GetMessage(1);
+        Expects(pPopup != nullptr);
+        Assert::IsNotNull(pPopup);
+        Assert::AreEqual(std::wstring(L"Loaded Game"), pPopup->GetTitle());
+        Assert::AreEqual(std::wstring(L"0 achievements, 0 points"), pPopup->GetDescription());
+        Assert::AreEqual(std::string("9743"), pPopup->GetImage().Name());
+
+        Assert::IsFalse(bDialogShown);
+    }
+
     TEST_METHOD(TestLoadGameNotify)
     {
         class NotifyHarness : public GameContext::NotifyTarget
@@ -480,6 +517,7 @@ public:
         // local achievement data for 7 should be merged with server achievement data
         pAch = game.Assets().FindAchievement(7U);
         Assert::IsNotNull(pAch);
+        Ensures(pAch != nullptr);
         Assert::AreEqual(std::wstring(L"Ach2b"), pAch->GetName());
         Assert::AreEqual(std::wstring(L"Desc2b"), pAch->GetDescription());
         Assert::AreEqual(std::wstring(L"Auth2"), pAch->GetAuthor()); // author not merged
@@ -491,6 +529,7 @@ public:
         // no server achievement, assign FirstLocalId
         pAch = game.Assets().FindAchievement(GameAssets::FirstLocalId);
         Assert::IsNotNull(pAch);
+        Ensures(pAch != nullptr);
         Assert::AreEqual(std::wstring(L"Ach3"), pAch->GetName());
         Assert::AreEqual(std::wstring(L"Desc3"), pAch->GetDescription());
         Assert::AreEqual(std::wstring(L"Auth3"), pAch->GetAuthor());
@@ -502,6 +541,7 @@ public:
         // no server achievement, assign next local id
         pAch = game.Assets().FindAchievement(GameAssets::FirstLocalId + 1);
         Assert::IsNotNull(pAch);
+        Ensures(pAch != nullptr);
         Assert::AreEqual(std::wstring(L"Ach4"), pAch->GetName());
         Assert::AreEqual(std::wstring(L"Desc4"), pAch->GetDescription());
         Assert::AreEqual(std::wstring(L"Auth4"), pAch->GetAuthor());
diff --git a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
index 94b1b4f2..b5ff8072 100644
--- a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
@@ -3400,6 +3400,7 @@ public:
     {
         AssetListViewModelHarness vmAssetList;
         const auto* pLocalBadges = vmAssetList.AddLocalBadgesModel();
+        Expects(pLocalBadges != nullptr);
         const std::wstring sLocalBadgeName = L"local\\1234.png";
 
         vmAssetList.SetGameId(22U);
@@ -3687,6 +3688,7 @@ public:
         AssetListViewModelHarness vmAssetList;
         vmAssetList.SetGameId(22U);
         const auto* pLocalBadges = vmAssetList.AddLocalBadgesModel();
+        Expects(pLocalBadges != nullptr);
         vmAssetList.AddAchievement(AssetCategory::Core, 5, L"Test1", L"Desc1", L"12345", "0xH1234=1");
         vmAssetList.AddAchievement(AssetCategory::Core, 7, L"Test2", L"Desc2", L"11111", "0xH1111=1");
         const std::wstring sBadge1 = L"local\\ABCD.png";
@@ -3777,6 +3779,7 @@ public:
         AssetListViewModelHarness vmAssetList;
         vmAssetList.SetGameId(22U);
         const auto* pLocalBadges = vmAssetList.AddLocalBadgesModel();
+        Expects(pLocalBadges != nullptr);
         vmAssetList.AddAchievement(AssetCategory::Core, 5, L"Test1", L"Desc1", L"12345", "0xH1234=1");
         vmAssetList.AddAchievement(AssetCategory::Core, 7, L"Test2", L"Desc2", L"11111", "0xH1111=1");
         const std::wstring sBadge1 = L"local\\ABCD.png";
@@ -4727,6 +4730,7 @@ public:
         const std::wstring sBadge1 = L"local\\ABCD.png";
         const std::wstring sBadge2 = L"local\\EFGH.png";
         const auto* pLocalBadges = vmAssetList.AddLocalBadgesModel();
+        Expects(pLocalBadges != nullptr);
         vmAssetList.AddAchievement(AssetCategory::Local, 5, L"Test1", L"Desc1", sBadge1, "0xH1234=1");
         vmAssetList.AddAchievement(AssetCategory::Local, 7, L"Test2", L"Desc2", sBadge2, "0xH1234=1");
 
diff --git a/tests/ui/viewmodels/TriggerConditionAsserts.hh b/tests/ui/viewmodels/TriggerConditionAsserts.hh
index a17beb54..bf41a30f 100644
--- a/tests/ui/viewmodels/TriggerConditionAsserts.hh
+++ b/tests/ui/viewmodels/TriggerConditionAsserts.hh
@@ -70,6 +70,8 @@ std::wstring ToString<ra::ui::viewmodels::TriggerOperandType>(
             return L"BCD";
         case ra::ui::viewmodels::TriggerOperandType::Float:
             return L"Float";
+        case ra::ui::viewmodels::TriggerOperandType::Inverted:
+            return L"Inverted";
         default:
             return std::to_wstring(static_cast<int>(nOperandType));
     }
diff --git a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
index e55f249b..6cad63b2 100644
--- a/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerConditionViewModel_Tests.cpp
@@ -128,6 +128,11 @@ public:
         Assert::IsTrue(vmCondition.HasSourceSize());
         Assert::AreEqual(MemSize::EightBit, vmCondition.GetSourceSize());
 
+        vmCondition.SetSourceType(TriggerOperandType::Inverted);
+        Assert::AreEqual(TriggerOperandType::Inverted, vmCondition.GetSourceType());
+        Assert::IsTrue(vmCondition.HasSourceSize());
+        Assert::AreEqual(MemSize::EightBit, vmCondition.GetSourceSize());
+
         vmCondition.SetSourceType(TriggerOperandType::Value);
         Assert::AreEqual(TriggerOperandType::Value, vmCondition.GetSourceType());
         Assert::IsFalse(vmCondition.HasSourceSize());
@@ -230,6 +235,16 @@ public:
         Assert::IsFalse(vmCondition.HasTarget());
         Assert::AreEqual(MemSize::EightBit, vmCondition.GetTargetSize()); // size copied from source
 
+        vmCondition.SetTargetType(TriggerOperandType::Value);
+        Assert::IsFalse(vmCondition.HasTargetSize());
+        Assert::IsFalse(vmCondition.HasTarget());
+        Assert::AreEqual(MemSize::ThirtyTwoBit, vmCondition.GetTargetSize()); // value always 32-bit
+
+        vmCondition.SetTargetType(TriggerOperandType::Inverted);
+        Assert::IsFalse(vmCondition.HasTargetSize());
+        Assert::IsFalse(vmCondition.HasTarget());
+        Assert::AreEqual(MemSize::EightBit, vmCondition.GetTargetSize()); // size copied from source
+
         vmCondition.SetOperator(TriggerOperatorType::NotEquals);
         Assert::IsTrue(vmCondition.HasTargetSize());
         Assert::IsTrue(vmCondition.HasTarget());
@@ -258,6 +273,10 @@ public:
         Assert::AreEqual(TriggerOperandType::Prior, vmCondition.GetTargetType());
         Assert::IsTrue(vmCondition.HasTargetSize());
 
+        vmCondition.SetTargetType(TriggerOperandType::Inverted);
+        Assert::AreEqual(TriggerOperandType::Inverted, vmCondition.GetTargetType());
+        Assert::IsTrue(vmCondition.HasTargetSize());
+
         vmCondition.SetTargetType(TriggerOperandType::Value);
         Assert::AreEqual(TriggerOperandType::Value, vmCondition.GetTargetType());
         Assert::IsFalse(vmCondition.HasTargetSize());
@@ -440,6 +459,7 @@ public:
         ParseAndRegenerate("0xH1234=d0xH1234"); // delta
         ParseAndRegenerate("0xH1234=p0xH1234"); // prior
         ParseAndRegenerate("0xH1234=b0xH1234"); // bcd
+        ParseAndRegenerate("0xH1234=~0xH1234"); // inverted
         ParseAndRegenerate("0xH1234=1234"); // value
         ParseAndRegenerate("0xH1234=f12.34"); // float
     }
@@ -762,6 +782,36 @@ public:
         Assert::AreEqual(std::wstring(L"0x0008 (indirect)\r\n[No code note]"), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
     }
 
+    TEST_METHOD(TestTooltipDoubleIndirectAddressValue)
+    {
+        IndirectAddressTriggerViewModelHarness vmTrigger;
+        vmTrigger.SetIsValue(true);
+        vmTrigger.Parse("I:0xH0001_I:0xH0002_M:0xH0003=4");
+        vmTrigger.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
+
+        const auto* pCondition1 = vmTrigger.Conditions().GetItemAt(0);
+        Expects(pCondition1 != nullptr);
+        const auto* pCondition2 = vmTrigger.Conditions().GetItemAt(1);
+        Expects(pCondition2 != nullptr);
+        const auto* pCondition3 = vmTrigger.Conditions().GetItemAt(2);
+        Expects(pCondition3 != nullptr);
+
+        Assert::IsFalse(pCondition1->IsIndirect());
+        Assert::IsTrue(pCondition2->IsIndirect());
+        Assert::IsTrue(pCondition3->IsIndirect());
+
+        // $0001 = 1, 1+2 = $0003, $0003 = 3, 3+3 = $0006
+        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));
+
+        // $0001 = 3, 3+2 = $0005, $0005 = 5, 5+3 = $0008
+        vmTrigger.SetMemory({ 1 }, 3);
+        Assert::AreEqual(std::wstring(L"0x0001\r\n[No code note]"), pCondition1->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
+        Assert::AreEqual(std::wstring(L"0x0005 (indirect)\r\n[No code note]"), pCondition2->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
+        Assert::AreEqual(std::wstring(L"0x0008 (indirect)\r\n[No code note]"), pCondition3->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
+    }
+
     TEST_METHOD(TestIsModifying)
     {
         TriggerConditionViewModelHarness condition;
@@ -1050,6 +1100,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Delta));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Prior));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::BCD));
+        Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Inverted));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660.25f, TriggerOperandType::Address));
 
         condition.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, false);
@@ -1059,6 +1110,7 @@ public:
         Assert::AreEqual(std::wstring(L"0xc3500"), TriggerConditionViewModel::FormatValue(800000U, TriggerOperandType::Address));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Delta));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Prior));
+        Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::Inverted));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660U, TriggerOperandType::BCD));
         Assert::AreEqual(std::wstring(L"0x1234"), TriggerConditionViewModel::FormatValue(4660.25f, TriggerOperandType::Address));
     }
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index 8001bb8c..8f7a7cfb 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -146,7 +146,7 @@ public:
         Assert::AreEqual((int)TriggerConditionType::Trigger, vmTrigger.ConditionTypes().GetItemAt(13)->GetId());
         Assert::AreEqual(std::wstring(L"Trigger"), vmTrigger.ConditionTypes().GetItemAt(13)->GetLabel());
 
-        Assert::AreEqual({ 6U }, vmTrigger.OperandTypes().Count());
+        Assert::AreEqual({ 7U }, vmTrigger.OperandTypes().Count());
         Assert::AreEqual((int)TriggerOperandType::Address, vmTrigger.OperandTypes().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L"Mem"), vmTrigger.OperandTypes().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)TriggerOperandType::Value, vmTrigger.OperandTypes().GetItemAt(1)->GetId());
@@ -159,6 +159,8 @@ public:
         Assert::AreEqual(std::wstring(L"BCD"), vmTrigger.OperandTypes().GetItemAt(4)->GetLabel());
         Assert::AreEqual((int)TriggerOperandType::Float, vmTrigger.OperandTypes().GetItemAt(5)->GetId());
         Assert::AreEqual(std::wstring(L"Float"), vmTrigger.OperandTypes().GetItemAt(5)->GetLabel());
+        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((int)MemSize::Bit_0, vmTrigger.OperandSizes().GetItemAt(0)->GetId());