Repository: RetroAchievements/RAIntegration
Pull request #669 (2021-01-24 17:16:40)

Support for invalid memory regions
Allows the client to register invalid memory regions. Any achievements referencing these regions are disabled and reported as unsupported (mimics behavior from RetroArch).

![image](https://user-images.githubusercontent.com/32680403/105637785-72a63800-5e2c-11eb-9b88-5df51986075b.png)

Unsupported achievements show up as a separate category in the overlay, appearing after any locked achievements and before any unlocked achievements:

![image](https://user-images.githubusercontent.com/32680403/105637827-ad0fd500-5e2c-11eb-8980-142d41b420fa.png)

Additionally, the memory inspector now clearly indicates unsupported memory.

![image](https://user-images.githubusercontent.com/32680403/105637850-d9c3ec80-5e2c-11eb-85a3-73edb7f4acd3.png)

Search results and bookmarks still show "00" instead of "--".


Patch:
diff --git a/RA_Integration.sln b/RA_Integration.sln
index 98bb088d..1280a162 100644
--- a/RA_Integration.sln
+++ b/RA_Integration.sln
@@ -22,8 +22,6 @@ Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Analysis|Win32 = Analysis|Win32
 		Analysis|x64 = Analysis|x64
-		AnalysisUnicode|Win32 = AnalysisUnicode|Win32
-		AnalysisUnicode|x64 = AnalysisUnicode|x64
 		Debug|Win32 = Debug|Win32
 		Debug|x64 = Debug|x64
 		Release|Win32 = Release|Win32
@@ -36,11 +34,6 @@ Global
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Analysis|Win32.Build.0 = Analysis|Win32
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Analysis|x64.ActiveCfg = Analysis|x64
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Analysis|x64.Build.0 = Analysis|x64
-		{825CB292-F069-4B27-82CF-3417746ACDF3}.Analysis|x64.Build.2 = ReleaseWithDebugUnicode|x64
-		{825CB292-F069-4B27-82CF-3417746ACDF3}.AnalysisUnicode|Win32.ActiveCfg = AnalysisUnicode|Win32
-		{825CB292-F069-4B27-82CF-3417746ACDF3}.AnalysisUnicode|Win32.Build.0 = AnalysisUnicode|Win32
-		{825CB292-F069-4B27-82CF-3417746ACDF3}.AnalysisUnicode|x64.ActiveCfg = AnalysisUnicode|x64
-		{825CB292-F069-4B27-82CF-3417746ACDF3}.AnalysisUnicode|x64.Build.0 = AnalysisUnicode|x64
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Debug|Win32.ActiveCfg = Debug|Win32
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Debug|Win32.Build.0 = Debug|Win32
 		{825CB292-F069-4B27-82CF-3417746ACDF3}.Debug|x64.ActiveCfg = Debug|x64
@@ -57,10 +50,6 @@ Global
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|Win32.Build.0 = Analysis|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|x64.ActiveCfg = Analysis|x64
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Analysis|x64.Build.0 = Analysis|x64
-		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.AnalysisUnicode|Win32.ActiveCfg = Analysis|Win32
-		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.AnalysisUnicode|Win32.Build.0 = Analysis|Win32
-		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.AnalysisUnicode|x64.ActiveCfg = Analysis|x64
-		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.AnalysisUnicode|x64.Build.0 = Analysis|x64
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Debug|Win32.ActiveCfg = Debug|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Debug|Win32.Build.0 = Debug|Win32
 		{876DEBDF-C7C8-4713-8CEF-EBDEE65306A7}.Debug|x64.ActiveCfg = Debug|x64
@@ -77,10 +66,6 @@ Global
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|Win32.Build.0 = Release|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|x64.ActiveCfg = Release|x64
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Analysis|x64.Build.0 = Release|x64
-		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.AnalysisUnicode|Win32.ActiveCfg = Release|Win32
-		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.AnalysisUnicode|Win32.Build.0 = Release|Win32
-		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.AnalysisUnicode|x64.ActiveCfg = Release|x64
-		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.AnalysisUnicode|x64.Build.0 = Release|x64
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Debug|Win32.ActiveCfg = Debug|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Debug|Win32.Build.0 = Debug|Win32
 		{9D55EBE7-1392-4FA1-A9B9-F022F764CE35}.Debug|x64.ActiveCfg = Debug|x64
@@ -96,10 +81,6 @@ Global
 		{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
-		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.AnalysisUnicode|Win32.ActiveCfg = Release|Win32
-		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.AnalysisUnicode|Win32.Build.0 = Release|Win32
-		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.AnalysisUnicode|x64.ActiveCfg = Release|x64
-		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.AnalysisUnicode|x64.Build.0 = Release|x64
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Debug|Win32.ActiveCfg = Debug|Win32
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Debug|Win32.Build.0 = Debug|Win32
 		{F78C2C8B-55DD-4252-A38F-F4D252D14DF8}.Debug|x64.ActiveCfg = Debug|x64
diff --git a/rcheevos b/rcheevos
index 4f139be4..9af350e2 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1,1 +1,1 @@
-Subproject commit 4f139be47e3efd030cf08214a89e32e54e145410
+Subproject commit 9af350e2d54dd954070d01367b62c5da1e6e81f3
diff --git a/src/Exports.cpp b/src/Exports.cpp
index b8ac5d80..900f79bb 100644
--- a/src/Exports.cpp
+++ b/src/Exports.cpp
@@ -373,6 +373,18 @@ static void ProcessAchievements()
                 break;
             }
 
+            case ra::services::AchievementRuntime::ChangeType::AchievementDisabled:
+            {
+                auto* vmAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
+                if (vmAchievement)
+                {
+                    RA_LOG_WARN("Achievement #%u disabled: address %s not supported by current core", pChange.nId, ra::ByteAddressToString(pChange.nValue));
+                    vmAchievement->SetState(ra::data::models::AssetState::Disabled);
+                }
+
+                break;
+            }
+
             case ra::services::AchievementRuntime::ChangeType::LeaderboardStarted:
             {
                 const auto* pLeaderboard = pGameContext.FindLeaderboard(pChange.nId);
diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj
index 28d74486..d9b0d86c 100644
--- a/src/RA_Integration.vcxproj
+++ b/src/RA_Integration.vcxproj
@@ -1,14 +1,6 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
-    <ProjectConfiguration Include="AnalysisUnicode|Win32">
-      <Configuration>AnalysisUnicode</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="AnalysisUnicode|x64">
-      <Configuration>AnalysisUnicode</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
     <ProjectConfiguration Include="Analysis|Win32">
       <Configuration>Analysis</Configuration>
       <Platform>Win32</Platform>
@@ -17,14 +9,6 @@
       <Configuration>Analysis</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
-    <ProjectConfiguration Include="DebugUnicode|Win32">
-      <Configuration>DebugUnicode</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="DebugUnicode|x64">
-      <Configuration>DebugUnicode</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
@@ -33,22 +17,6 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
-    <ProjectConfiguration Include="ReleaseUnicode|Win32">
-      <Configuration>ReleaseUnicode</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="ReleaseUnicode|x64">
-      <Configuration>ReleaseUnicode</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="ReleaseWithDebugUnicode|Win32">
-      <Configuration>ReleaseWithDebugUnicode</Configuration>
-      <Platform>Win32</Platform>
-    </ProjectConfiguration>
-    <ProjectConfiguration Include="ReleaseWithDebugUnicode|x64">
-      <Configuration>ReleaseWithDebugUnicode</Configuration>
-      <Platform>x64</Platform>
-    </ProjectConfiguration>
     <ProjectConfiguration Include="ReleaseWithDebug|Win32">
       <Configuration>ReleaseWithDebug</Configuration>
       <Platform>Win32</Platform>
@@ -88,20 +56,12 @@
     <ClCompile Include="data\models\CapturedTriggerHits.cpp" />
     <ClCompile Include="data\models\LocalBadgesModel.cpp" />
     <ClCompile Include="pch.cpp">
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|Win32'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|x64'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|Win32'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|x64'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|Win32'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
       <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|x64'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|Win32'">Create</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|x64'">Create</PrecompiledHeader>
     </ClCompile>
@@ -410,48 +370,24 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|x64'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <PlatformToolset>v141_xp</PlatformToolset>
-    <CharacterSet>MultiByte</CharacterSet>
-    <WholeProgramOptimization>true</WholeProgramOptimization>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|x64'" Label="Configuration">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|Win32'" Label="Configuration">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|x64'" Label="Configuration">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <PlatformToolset>v141_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
@@ -459,43 +395,23 @@
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>MultiByte</CharacterSet>
+    <CharacterSet>Unicode</CharacterSet>
     <PlatformToolset>v141_xp</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>MultiByte</CharacterSet>
     <PlatformToolset>v141_xp</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Analysis|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>MultiByte</CharacterSet>
+    <CharacterSet>Unicode</CharacterSet>
     <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Analysis|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>MultiByte</CharacterSet>
-    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|Win32'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>Unicode</CharacterSet>
-    <PlatformToolset>v141_xp</PlatformToolset>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|x64'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>Unicode</CharacterSet>
-    <PlatformToolset>v141_xp</PlatformToolset>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|Win32'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
-    <CharacterSet>Unicode</CharacterSet>
     <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|x64'" Label="Configuration">
-    <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
-    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -516,22 +432,6 @@
     <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)'=='ReleaseUnicode|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)'=='ReleaseUnicode|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)'=='ReleaseWithDebugUnicode|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)'=='ReleaseWithDebugUnicode|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)'=='Debug|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" />
@@ -548,22 +448,6 @@
     <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)'=='DebugUnicode|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)'=='DebugUnicode|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)'=='AnalysisUnicode|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)'=='AnalysisUnicode|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>
     <_ProjectFileVersion>11.0.50727.1</_ProjectFileVersion>
@@ -598,32 +482,6 @@
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|Win32'">
-    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
-    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
-    <TargetName>RA_Integration</TargetName>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|x64'">
-    <TargetName>RA_Integration</TargetName>
-    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
-    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|Win32'">
-    <TargetName>RA_Integration</TargetName>
-    <CodeAnalysisRuleSet>ra_rules.ruleset</CodeAnalysisRuleSet>
-    <RunCodeAnalysis>true</RunCodeAnalysis>
-    <CAExcludePath>$(RapidJSON_IncludeDir);$(CAExcludePath)</CAExcludePath>
-    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
-    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|x64'">
-    <TargetName>RA_Integration</TargetName>
-    <CodeAnalysisRuleSet>ra_rules.ruleset</CodeAnalysisRuleSet>
-    <RunCodeAnalysis>true</RunCodeAnalysis>
-    <CAExcludePath>$(RapidJSON_IncludeDir);$(CAExcludePath)</CAExcludePath>
-    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
-    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
-  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
@@ -648,30 +506,6 @@
     <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
     <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|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)'=='ReleaseUnicode|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)'=='ReleaseWithDebugUnicode|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)'=='ReleaseWithDebugUnicode|x64'">
-    <LinkIncremental>false</LinkIncremental>
-    <TargetName>RA_Integration</TargetName>
-    <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
-    <IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\</IntDir>
-  </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
@@ -777,103 +611,6 @@
       </Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|Win32'">
-    <ClCompile>
-      <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>_DEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
-      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
-      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
-      <MultiProcessorCompilation>true</MultiProcessorCompilation>
-      <ConformanceMode>true</ConformanceMode>
-      <ControlFlowGuard>false</ControlFlowGuard>
-    </ClCompile>
-    <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <GenerateDebugInformation>true</GenerateDebugInformation>
-      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <SubSystem>Windows</SubSystem>
-    </Link>
-    <ResourceCompile>
-      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ResourceCompile>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugUnicode|x64'">
-    <ClCompile>
-      <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>_DEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
-      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
-      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <MultiProcessorCompilation>true</MultiProcessorCompilation>
-      <ConformanceMode>true</ConformanceMode>
-      <ControlFlowGuard>false</ControlFlowGuard>
-    </ClCompile>
-    <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <GenerateDebugInformation>true</GenerateDebugInformation>
-      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <SubSystem>Windows</SubSystem>
-    </Link>
-    <ResourceCompile>
-      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ResourceCompile>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|Win32'">
-    <ClCompile>
-      <PreprocessorDefinitions>NDEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
-      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
-      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <MultiProcessorCompilation>true</MultiProcessorCompilation>
-      <EnablePREfast>true</EnablePREfast>
-      <Optimization>Disabled</Optimization>
-      <ControlFlowGuard>false</ControlFlowGuard>
-    </ClCompile>
-    <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <GenerateDebugInformation>false</GenerateDebugInformation>
-      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <SubSystem>Windows</SubSystem>
-    </Link>
-    <ResourceCompile>
-      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ResourceCompile>
-    <PostBuildEvent>
-      <Command />
-    </PostBuildEvent>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='AnalysisUnicode|x64'">
-    <ClCompile>
-      <PreprocessorDefinitions>NDEBUG;RA_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
-      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
-      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <MultiProcessorCompilation>true</MultiProcessorCompilation>
-      <EnablePREfast>true</EnablePREfast>
-      <Optimization>Disabled</Optimization>
-      <ControlFlowGuard>false</ControlFlowGuard>
-    </ClCompile>
-    <Link>
-      <AdditionalDependencies>windowscodecs.lib;winmm.lib;Winhttp.lib;user32.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <GenerateDebugInformation>false</GenerateDebugInformation>
-      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <SubSystem>Windows</SubSystem>
-    </Link>
-    <ResourceCompile>
-      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ResourceCompile>
-    <PostBuildEvent>
-      <Command>
-      </Command>
-    </PostBuildEvent>
-  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <ClCompile>
       <Optimization>Full</Optimization>
@@ -956,86 +693,6 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
     </Link>
   </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|Win32'">
-    <ClCompile>
-      <Optimization>Full</Optimization>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <WholeProgramOptimization>false</WholeProgramOptimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <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;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <SubSystem>Windows</SubSystem>
-      <GenerateDebugInformation>false</GenerateDebugInformation>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseUnicode|x64'">
-    <ClCompile>
-      <Optimization>Full</Optimization>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <WholeProgramOptimization>false</WholeProgramOptimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <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;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <SubSystem>Windows</SubSystem>
-      <GenerateDebugInformation>false</GenerateDebugInformation>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|Win32'">
-    <ClCompile>
-      <Optimization>Full</Optimization>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <WholeProgramOptimization>false</WholeProgramOptimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <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;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <SubSystem>Windows</SubSystem>
-      <GenerateDebugInformation>true</GenerateDebugInformation>
-    </Link>
-  </ItemDefinitionGroup>
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseWithDebugUnicode|x64'">
-    <ClCompile>
-      <Optimization>Full</Optimization>
-      <IntrinsicFunctions>true</IntrinsicFunctions>
-      <WholeProgramOptimization>false</WholeProgramOptimization>
-      <AdditionalIncludeDirectories>..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <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;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <SubSystem>Windows</SubSystem>
-      <GenerateDebugInformation>true</GenerateDebugInformation>
-    </Link>
-  </ItemDefinitionGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
diff --git a/src/data/context/ConsoleContext.cpp b/src/data/context/ConsoleContext.cpp
index 322db104..07ee2519 100644
--- a/src/data/context/ConsoleContext.cpp
+++ b/src/data/context/ConsoleContext.cpp
@@ -3,7 +3,7 @@
 #include "RA_Log.h"
 #include "RA_StringUtils.h"
 
-#include <rconsoles.h>
+#include <rc_consoles.h>
 
 namespace ra {
 namespace data {
diff --git a/src/data/context/EmulatorContext.cpp b/src/data/context/EmulatorContext.cpp
index 3e66a555..1649ab16 100644
--- a/src/data/context/EmulatorContext.cpp
+++ b/src/data/context/EmulatorContext.cpp
@@ -10,6 +10,7 @@
 #include "data\context\GameContext.hh"
 #include "data\context\UserContext.hh"
 
+#include "services\AchievementRuntime.hh"
 #include "services\IClock.hh"
 #include "services\IConfiguration.hh"
 #include "services\IFileSystem.hh"
@@ -525,22 +526,60 @@ void EmulatorContext::OnTotalMemorySizeChanged()
     }
 }
 
-uint8_t EmulatorContext::ReadMemoryByte(ra::ByteAddress nAddress) const noexcept
+bool EmulatorContext::HasInvalidRegions() const noexcept
 {
+    for (const auto& pBlock : m_vMemoryBlocks)
+    {
+        if (!pBlock.read)
+            return true;
+    }
+
+    return false;
+}
+
+bool EmulatorContext::IsValidAddress(ra::ByteAddress nAddress) const noexcept
+{
+    const ra::ByteAddress nOriginalAddress = nAddress;
+    for (const auto& pBlock : m_vMemoryBlocks)
+    {
+        if (nAddress < pBlock.size)
+        {
+            if (pBlock.read)
+                return true;
+
+            break;
+        }
+
+        nAddress -= gsl::narrow_cast<ra::ByteAddress>(pBlock.size);
+    }
+
+    return false;
+}
+
+uint8_t EmulatorContext::ReadMemoryByte(ra::ByteAddress nAddress) const
+{
+    const ra::ByteAddress nOriginalAddress = nAddress;
     for (const auto& pBlock : m_vMemoryBlocks)
     {
         if (nAddress < pBlock.size)
-            return pBlock.read(nAddress);
+        {
+            if (pBlock.read)
+                return pBlock.read(nAddress);
+
+            break;
+        }
 
         nAddress -= gsl::narrow_cast<ra::ByteAddress>(pBlock.size);
     }
 
-    return 0U;
+    ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
+    return 0;
 }
 
 _Use_decl_annotations_
 void EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[], size_t nCount) const
 {
+    const ra::ByteAddress nOriginalAddress = nAddress;
     Expects(pBuffer != nullptr);
 
     for (const auto& pBlock : m_vMemoryBlocks)
@@ -551,6 +590,12 @@ void EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[], si
             continue;
         }
 
+        if (!pBlock.read)
+        {
+            ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
+            break;
+        }
+
         const size_t nBlockRemaining = pBlock.size - nAddress;
         size_t nToRead = std::min(nCount, nBlockRemaining);
         nCount -= nToRead;
@@ -644,6 +689,9 @@ void EmulatorContext::WriteMemoryByte(ra::ByteAddress nAddress, uint8_t nValue)
     {
         if (nAddress < pBlock.size)
         {
+            if (!pBlock.write)
+                return;
+
             pBlock.write(nAddress, nValue);
             m_bMemoryModified = true;
 
diff --git a/src/data/context/EmulatorContext.hh b/src/data/context/EmulatorContext.hh
index 1859adf6..a139056f 100644
--- a/src/data/context/EmulatorContext.hh
+++ b/src/data/context/EmulatorContext.hh
@@ -185,6 +185,16 @@ public:
     /// </summary>
     size_t TotalMemorySize() const noexcept { return m_nTotalMemorySize; }
 
+    /// <summary>
+    /// Determines if any invalid regions were registered.
+    /// </summary>
+    bool HasInvalidRegions() const noexcept;
+
+    /// <summary>
+    /// Determines if the specified address is valid.
+    /// </summary>
+    bool IsValidAddress(ra::ByteAddress nAddress) const noexcept;
+
     /// <summary>
     /// Reads memory from the emulator.
     /// </summary>
@@ -193,7 +203,7 @@ public:
     /// <summary>
     /// Reads memory from the emulator.
     /// </summary>
-    uint8_t ReadMemoryByte(ra::ByteAddress nAddress) const noexcept;
+    uint8_t ReadMemoryByte(ra::ByteAddress nAddress) const;
 
     /// <summary>
     /// Reads memory from the emulator.
diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp
index 06ed9b07..01a4a717 100644
--- a/src/data/context/GameContext.cpp
+++ b/src/data/context/GameContext.cpp
@@ -305,12 +305,16 @@ void GameContext::UpdateUnlocks(const std::set<unsigned int>& vUnlockedAchieveme
         vLockedAchievements.insert(pAchievement->ID());
 
     // deactivate anything the player has already unlocked
+    size_t nUnlockedCoreAchievements = 0U;
     for (const auto nUnlockedAchievement : vUnlockedAchievements)
     {
         vLockedAchievements.erase(nUnlockedAchievement);
-        auto* pAchievement = FindAchievement(nUnlockedAchievement);
+        auto* pAchievement = Assets().FindAchievement(nUnlockedAchievement);
         if (pAchievement != nullptr)
-            pAchievement->SetActive(false);
+        {
+            pAchievement->SetState(ra::data::models::AssetState::Inactive);
+            ++nUnlockedCoreAchievements;
+        }
     }
 
 #ifndef RA_UTEST
@@ -319,14 +323,15 @@ void GameContext::UpdateUnlocks(const std::set<unsigned int>& vUnlockedAchieveme
     // activate any core achievements the player hasn't earned and pre-fetch the locked image
     for (auto nAchievementId : vLockedAchievements)
     {
-        auto* pAchievement = FindAchievement(nAchievementId);
-        if (pAchievement && pAchievement->GetCategory() == Achievement::Category::Core)
+        auto* pAchievement = Assets().FindAchievement(nAchievementId);
+        if (pAchievement && pAchievement->GetCategory() == ra::data::models::AssetCategory::Core)
         {
-            pAchievement->SetActive(true);
+            if (!pAchievement->IsActive() && pAchievement->GetState() != ra::data::models::AssetState::Disabled)
+                pAchievement->SetState(ra::data::models::AssetState::Waiting);
 
 #ifndef RA_UTEST
-            if (!pAchievement->BadgeImageURI().empty())
-                pImageRepository.FetchImage(ra::ui::ImageType::Badge, pAchievement->BadgeImageURI() + "_lock");
+            if (!pAchievement->GetBadge().empty())
+                pImageRepository.FetchImage(ra::ui::ImageType::Badge, ra::Narrow(pAchievement->GetBadge()) + "_lock");
 #endif
         }
     }
@@ -339,11 +344,11 @@ void GameContext::UpdateUnlocks(const std::set<unsigned int>& vUnlockedAchieveme
         auto* pPopup = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().GetMessage(nPopup);
         if (pPopup != nullptr)
         {
-            size_t nUnlockedCoreAchievements = 0U;
-            for (auto& pAchievement : m_vAchievements)
+            const auto nDisabledAchievements = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().DetectUnsupportedAchievements();
+            if (nDisabledAchievements != 0)
             {
-                if (pAchievement->GetCategory() == Achievement::Category::Core && !pAchievement->Active())
-                    ++nUnlockedCoreAchievements;
+                const auto sDescription = ra::StringPrintf(L"%s (%s unsupported)", pPopup->GetDescription(), nDisabledAchievements);
+                pPopup->SetDescription(sDescription);
             }
 
             pPopup->SetDetail(ra::StringPrintf(L"You have earned %u achievements", nUnlockedCoreAchievements));
diff --git a/src/data/models/AchievementModel.cpp b/src/data/models/AchievementModel.cpp
index 0ecc84fe..884cb834 100644
--- a/src/data/models/AchievementModel.cpp
+++ b/src/data/models/AchievementModel.cpp
@@ -122,7 +122,7 @@ void AchievementModel::DoFrame()
     const auto* pTrigger = pRuntime.GetAchievementTrigger(GetID());
     if (pTrigger == nullptr)
     {
-        if (GetState() != AssetState::Triggered)
+        if (IsActive())
             SetState(AssetState::Inactive);
     }
     else
@@ -144,11 +144,13 @@ void AchievementModel::DoFrame()
             case RC_TRIGGER_STATE_WAITING:
                 SetState(AssetState::Waiting);
                 break;
+            case RC_TRIGGER_STATE_DISABLED:
+                SetState(AssetState::Disabled);
+                break;
         }
     }
 }
 
-
 void AchievementModel::HandleStateChanged(AssetState nOldState, AssetState nNewState)
 {
     if (!ra::services::ServiceLocator::Exists<ra::services::AchievementRuntime>())
diff --git a/src/data/models/AssetModelBase.cpp b/src/data/models/AssetModelBase.cpp
index f78fee56..f15ab45d 100644
--- a/src/data/models/AssetModelBase.cpp
+++ b/src/data/models/AssetModelBase.cpp
@@ -540,6 +540,7 @@ bool AssetModelBase::IsActive(AssetState nState) noexcept
     {
         case AssetState::Inactive:
         case AssetState::Triggered:
+        case AssetState::Disabled:
             return false;
         default:
             return true;
diff --git a/src/data/models/AssetModelBase.hh b/src/data/models/AssetModelBase.hh
index bbb6f572..a8ae4a00 100644
--- a/src/data/models/AssetModelBase.hh
+++ b/src/data/models/AssetModelBase.hh
@@ -35,6 +35,7 @@ enum class AssetState
     Triggered,
     Waiting,
     Paused,
+    Disabled,
 };
 
 enum class AssetChanges
diff --git a/src/services/AchievementRuntime.cpp b/src/services/AchievementRuntime.cpp
index 3bebf852..60eccaf5 100644
--- a/src/services/AchievementRuntime.cpp
+++ b/src/services/AchievementRuntime.cpp
@@ -38,6 +38,7 @@ void AchievementRuntime::ResetRuntime() noexcept
 
 int AchievementRuntime::ActivateAchievement(ra::AchievementID nId, const std::string& sTrigger)
 {
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     EnsureInitialized();
 
     if (m_bPaused)
@@ -53,7 +54,7 @@ int AchievementRuntime::ActivateAchievement(ra::AchievementID nId, const std::st
     return rc_runtime_activate_achievement(&m_pRuntime, nId, sTrigger.c_str(), nullptr, 0);
 }
 
-rc_trigger_t* AchievementRuntime::GetAchievementTrigger(ra::AchievementID nId, const std::string& sTrigger) noexcept
+rc_trigger_t* AchievementRuntime::GetAchievementTrigger(ra::AchievementID nId, const std::string& sTrigger)
 {
     if (!m_bInitialized)
         return nullptr;
@@ -67,6 +68,7 @@ rc_trigger_t* AchievementRuntime::GetAchievementTrigger(ra::AchievementID nId, c
     md5_append(&state, bytes, gsl::narrow_cast<int>(sTrigger.length()));
     md5_finish(&state, md5);
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     for (size_t i = 0; i < m_pRuntime.trigger_count; ++i)
     {
         if (m_pRuntime.triggers[i].id == nId && memcmp(m_pRuntime.triggers[i].md5, md5, sizeof(md5)) == 0)
@@ -81,7 +83,7 @@ rc_trigger_t* AchievementRuntime::GetAchievementTrigger(ra::AchievementID nId, c
     return nullptr;
 }
 
-void AchievementRuntime::DeactivateAchievement(ra::AchievementID nId) noexcept
+void AchievementRuntime::DeactivateAchievement(ra::AchievementID nId)
 {
     // NOTE: rc_runtime_deactivate_achievement will either reset the trigger or free it
     // we want to keep it so the user can examine the hit counts after an incorrect trigger
@@ -89,11 +91,12 @@ void AchievementRuntime::DeactivateAchievement(ra::AchievementID nId) noexcept
     DetachAchievementTrigger(nId);
 }
 
-rc_trigger_t* AchievementRuntime::DetachAchievementTrigger(ra::AchievementID nId) noexcept
+rc_trigger_t* AchievementRuntime::DetachAchievementTrigger(ra::AchievementID nId)
 {
     if (!m_bInitialized)
         return nullptr;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     for (unsigned i = 0; i < m_pRuntime.trigger_count; ++i)
     {
         if (m_pRuntime.triggers[i].id == nId)
@@ -110,11 +113,13 @@ rc_trigger_t* AchievementRuntime::DetachAchievementTrigger(ra::AchievementID nId
     return nullptr;
 }
 
-void AchievementRuntime::ReleaseAchievementTrigger(ra::AchievementID nId, _In_ rc_trigger_t* pTrigger) noexcept
+void AchievementRuntime::ReleaseAchievementTrigger(ra::AchievementID nId, _In_ rc_trigger_t* pTrigger)
 {
     if (!m_bInitialized)
         return;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
 #pragma warning(push)
 #pragma warning(disable:6001) // Using uninitialized memory 'pTrigger'
 
@@ -137,11 +142,12 @@ void AchievementRuntime::ReleaseAchievementTrigger(ra::AchievementID nId, _In_ r
 #pragma warning(pop)
 }
 
-void AchievementRuntime::UpdateAchievementId(ra::AchievementID nOldId, ra::AchievementID nNewId) noexcept
+void AchievementRuntime::UpdateAchievementId(ra::AchievementID nOldId, ra::AchievementID nNewId)
 {
     if (!m_bInitialized)
         return;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     for (unsigned i = 0; i < m_pRuntime.trigger_count; ++i)
     {
         if (m_pRuntime.triggers[i].id == nOldId)
@@ -149,15 +155,17 @@ void AchievementRuntime::UpdateAchievementId(ra::AchievementID nOldId, ra::Achie
     }
 }
 
-int AchievementRuntime::ActivateLeaderboard(unsigned int nId, const std::string& sDefinition) noexcept
+int AchievementRuntime::ActivateLeaderboard(unsigned int nId, const std::string& sDefinition)
 {
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     EnsureInitialized();
 
     return rc_runtime_activate_lboard(&m_pRuntime, nId, sDefinition.c_str(), nullptr, 0);
 }
 
-void AchievementRuntime::ActivateRichPresence(const std::string& sScript) noexcept
+void AchievementRuntime::ActivateRichPresence(const std::string& sScript)
 {
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     EnsureInitialized();
 
     if (sScript.empty())
@@ -176,6 +184,8 @@ std::wstring AchievementRuntime::GetRichPresenceDisplayString() const
 {
     if (m_bInitialized)
     {
+        std::lock_guard<std::mutex> pLock(m_pMutex);
+
         if (m_nRichPresenceParseResult != RC_OK)
             return ra::StringPrintf(L"Parse error %d: %s\n", m_nRichPresenceParseResult, rc_error_str(m_nRichPresenceParseResult));
 
@@ -216,6 +226,9 @@ static void map_event_to_change(const rc_runtime_event_t* pRuntimeEvent)
         case RC_RUNTIME_EVENT_ACHIEVEMENT_RESET:
             nChangeType = AchievementRuntime::ChangeType::AchievementReset;
             break;
+        case RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED:
+            nChangeType = AchievementRuntime::ChangeType::AchievementDisabled;
+            break;
         case RC_RUNTIME_EVENT_LBOARD_STARTED:
             nChangeType = AchievementRuntime::ChangeType::LeaderboardStarted;
             break;
@@ -228,6 +241,9 @@ static void map_event_to_change(const rc_runtime_event_t* pRuntimeEvent)
         case RC_RUNTIME_EVENT_LBOARD_TRIGGERED:
             nChangeType = AchievementRuntime::ChangeType::LeaderboardTriggered;
             break;
+        case RC_RUNTIME_EVENT_LBOARD_DISABLED:
+            nChangeType = AchievementRuntime::ChangeType::LeaderboardDisabled;
+            break;
         default:
             // unsupported
             return;
@@ -236,11 +252,12 @@ static void map_event_to_change(const rc_runtime_event_t* pRuntimeEvent)
     g_pChanges->emplace_back(AchievementRuntime::Change{ nChangeType, pRuntimeEvent->id, pRuntimeEvent->value });
 }
 
-_Use_decl_annotations_ void AchievementRuntime::Process(std::vector<Change>& changes) noexcept
+_Use_decl_annotations_ void AchievementRuntime::Process(std::vector<Change>& changes)
 {
     if (!m_bInitialized || m_bPaused)
         return;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
     g_pChanges = &changes;
     rc_runtime_do_frame(&m_pRuntime, map_event_to_change, rc_peek_callback, nullptr, nullptr);
     g_pChanges = nullptr;
@@ -621,6 +638,8 @@ bool AchievementRuntime::LoadProgressFromFile(const char* sLoadStateFilename)
     if (!m_bInitialized)
         return true;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
     // reset the runtime state, then apply state from file
     rc_runtime_reset(&m_pRuntime);
 
@@ -697,6 +716,8 @@ bool AchievementRuntime::LoadProgressFromBuffer(const char* pBuffer)
     if (!m_bInitialized)
         return true;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
     // reset the runtime state, then apply state from file
     rc_runtime_reset(&m_pRuntime);
 
@@ -731,6 +752,8 @@ void AchievementRuntime::SaveProgressToFile(const char* sSaveStateFilename) cons
         return;
     }
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
     const auto nSize = rc_runtime_progress_size(&m_pRuntime, nullptr);
     std::string sSerialized;
     sSerialized.resize(nSize);
@@ -746,6 +769,8 @@ int AchievementRuntime::SaveProgressToBuffer(char* pBuffer, int nBufferSize) con
     if (!pUserContext.IsLoggedIn())
         return 0;
 
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
     const auto nSize = rc_runtime_progress_size(&m_pRuntime, nullptr);
     if (nSize <= nBufferSize)
     {
@@ -760,6 +785,62 @@ int AchievementRuntime::SaveProgressToBuffer(char* pBuffer, int nBufferSize) con
     return nSize;
 }
 
+void AchievementRuntime::InvalidateAddress(ra::ByteAddress nAddress) noexcept
+{
+    // this should only be called from DetectUnsupportedAchievements or indirectly via Process,
+    // both of which aquire the lock, so we shouldn't try to acquire it here.
+    // we can also avoid checking m_bInitialized
+    rc_runtime_invalidate_address(&m_pRuntime, nAddress);
+}
+
+#pragma warning(push)
+#pragma warning(disable : 5045)
+
+size_t AchievementRuntime::DetectUnsupportedAchievements()
+{
+    const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
+    std::lock_guard<std::mutex> pLock(m_pMutex);
+
+    const rc_memref_t* memref = m_pRuntime.memrefs;
+    while (memref)
+    {
+        // ignore indirect memory references as their value is calculated from other memory and can easily point at
+        // invalid addresses.
+        if (!memref->value.is_indirect)
+        {
+            // attempt to read one byte from the memory address (assume multi-byte reads won't span regions). if the
+            // read fails, EmulatorContext will call InvalidateAddress, which will flag all associated achievements.
+            if (!pEmulatorContext.IsValidAddress(memref->address))
+                InvalidateAddress(memref->address);
+        }
+
+        memref = memref->next;
+    }
+
+    size_t nDisabled = 0;
+    const auto* pTrigger = m_pRuntime.triggers;
+    const auto* pStop = pTrigger + m_pRuntime.trigger_count;
+    while (pTrigger < pStop)
+    {
+        if (pTrigger->invalid_memref)
+        {
+            // before rc_runtime_do_frame called
+            ++nDisabled;
+        }
+        else if (pTrigger->trigger && pTrigger->trigger->state == RC_TRIGGER_STATE_DISABLED)
+        {
+            // after rc_runtime_do_frame called
+            ++nDisabled;
+        }
+
+        ++pTrigger;
+    }
+
+    return nDisabled;
+}
+
+#pragma warning(pop)
+
 } // namespace services
 } // namespace ra
 
diff --git a/src/services/AchievementRuntime.hh b/src/services/AchievementRuntime.hh
index 95adc265..f0d3cb99 100644
--- a/src/services/AchievementRuntime.hh
+++ b/src/services/AchievementRuntime.hh
@@ -42,12 +42,12 @@ public:
     /// <summary>
     /// Removes an achievement from the processing queue.
     /// </summary>
-    void DeactivateAchievement(ra::AchievementID nId) noexcept;
+    void DeactivateAchievement(ra::AchievementID nId);
 
     /// <summary>
     /// Updates any references using the provided ID to a new value
     /// </summary>
-    void UpdateAchievementId(ra::AchievementID nOldId, ra::AchievementID nNewId) noexcept;
+    void UpdateAchievementId(ra::AchievementID nOldId, ra::AchievementID nNewId);
 
     /// <summary>
     /// Gets the raw trigger for the achievement (if active)
@@ -74,22 +74,22 @@ public:
     /// <summary>
     /// Gets the raw trigger for the achievement (if ever active)
     /// </summary>
-    rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId, const std::string& sTrigger) noexcept;
+    rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId, const std::string& sTrigger);
 
     /// <summary>
     /// Gets the raw trigger for the achievement (if active)
     /// </summary>
-    rc_trigger_t* DetachAchievementTrigger(ra::AchievementID nId) noexcept;
+    rc_trigger_t* DetachAchievementTrigger(ra::AchievementID nId);
 
     /// <summary>
     /// Removes an achievement from the processing queue.
     /// </summary>
-    void ReleaseAchievementTrigger(ra::AchievementID nId, _In_ rc_trigger_t* pTrigger) noexcept;
+    void ReleaseAchievementTrigger(ra::AchievementID nId, _In_ rc_trigger_t* pTrigger);
 
     /// <summary>
     /// Adds a leaderboard to the processing queue.
     /// </summary>
-    int ActivateLeaderboard(unsigned int nId, const std::string& sDefinition) noexcept;
+    int ActivateLeaderboard(unsigned int nId, const std::string& sDefinition);
 
     /// <summary>
     /// Removes a leaderboard from the processing queue.
@@ -104,7 +104,7 @@ public:
     /// Specifies the rich presence to process each frame.
     /// </summary>
     /// <remarks>Only updates the memrefs each frame (for deltas), the script is not processed here.</remarks>
-    void ActivateRichPresence(const std::string& sScript) noexcept;
+    void ActivateRichPresence(const std::string& sScript);
 
     /// <summary>
     /// Gets whether or not the loaded game has a rich presence script.
@@ -132,6 +132,8 @@ public:
         LeaderboardCanceled,
         LeaderboardUpdated,
         LeaderboardTriggered,
+        AchievementDisabled,
+        LeaderboardDisabled,
     };
 
     struct Change
@@ -144,7 +146,7 @@ public:
     /// <summary>
     /// Processes all active achievements for the current frame.
     /// </summary>
-    virtual void Process(_Inout_ std::vector<Change>& changes) noexcept;
+    virtual void Process(_Inout_ std::vector<Change>& changes);
 
     /// <summary>
     /// Loads HitCount data for active achievements from a save state file.
@@ -197,9 +199,14 @@ public:
             rc_runtime_reset(&m_pRuntime);
     }
 
+    void InvalidateAddress(ra::ByteAddress nAddress) noexcept;
+
+    size_t DetectUnsupportedAchievements();
+
 protected:
     bool m_bPaused = false;
     rc_runtime_t m_pRuntime{};
+    mutable std::mutex m_pMutex;
 
 private:
     bool LoadProgressV1(const std::string& sProgress, std::set<unsigned int>& vProcessedAchievementIds);
diff --git a/src/services/impl/JsonFileConfiguration.cpp b/src/services/impl/JsonFileConfiguration.cpp
index 21e15767..55b681d6 100644
--- a/src/services/impl/JsonFileConfiguration.cpp
+++ b/src/services/impl/JsonFileConfiguration.cpp
@@ -331,7 +331,7 @@ void JsonFileConfiguration::UpdateHost()
     if (m_sHostName.empty())
     {
         m_sHostName = "retroachievements.org";
-        m_sHostUrl = "http://retroachievements.org";
+        m_sHostUrl = "https://retroachievements.org";
         m_sImageHostUrl = "http://i.retroachievements.org";
     }
     else
@@ -351,6 +351,8 @@ void JsonFileConfiguration::UpdateHost()
 
         m_sImageHostUrl = m_sHostUrl;
     }
+
+    RA_LOG_INFO("Using server: %s", m_sHostUrl.c_str());
 }
 
 } // namespace impl
diff --git a/src/services/impl/WindowsHttpRequester.cpp b/src/services/impl/WindowsHttpRequester.cpp
index 50f3b397..d63273c9 100644
--- a/src/services/impl/WindowsHttpRequester.cpp
+++ b/src/services/impl/WindowsHttpRequester.cpp
@@ -9,6 +9,8 @@
 
 #include <winhttp.h>
 
+//#define ALLOW_INVALID_SSL_CERTIFICATES
+
 namespace ra {
 namespace services {
 namespace impl {
@@ -167,24 +169,54 @@ unsigned int WindowsHttpRequester::Request(const Http::Request& pRequest, TextWr
                 sHeaders += ra::Widen(pRequest.GetContentType());
 
                 BOOL bResults{};
+                bool retry;
 
-                // send the request
-                if (sPostData.empty())
+                do
                 {
-                    bResults = WinHttpSendRequest(hRequest,
-                        sHeaders.c_str(), gsl::narrow_cast<int>(sHeaders.length()),
-                        WINHTTP_NO_REQUEST_DATA,
-                        0, 0,
-                        0);
-                }
-                else
-                {
-                    bResults = WinHttpSendRequest(hRequest,
-                        sHeaders.c_str(), gsl::narrow_cast<int>(sHeaders.length()),
-                        static_cast<LPVOID>(sPostData.data()),
-                        gsl::narrow_cast<int>(sPostData.length()), gsl::narrow_cast<int>(sPostData.length()),
-                        0);
-                }
+                    retry = false;
+
+                    // send the request
+                    if (sPostData.empty())
+                    {
+                        bResults = WinHttpSendRequest(hRequest,
+                            sHeaders.c_str(), gsl::narrow_cast<int>(sHeaders.length()),
+                            WINHTTP_NO_REQUEST_DATA,
+                            0, 0,
+                            0);
+                    }
+                    else
+                    {
+                        bResults = WinHttpSendRequest(hRequest,
+                            sHeaders.c_str(), gsl::narrow_cast<int>(sHeaders.length()),
+                            static_cast<LPVOID>(sPostData.data()),
+                            gsl::narrow_cast<int>(sPostData.length()), gsl::narrow_cast<int>(sPostData.length()),
+                            0);
+                    }
+
+                    if (!bResults)
+                    {
+                        nStatusCode = GetLastError();
+
+                        if (nStatusCode == ERROR_WINHTTP_RESEND_REQUEST)
+                        {
+                            retry = true;
+                        }
+#ifdef ALLOW_INVALID_SSL_CERTIFICATES
+                        else if (nStatusCode == ERROR_WINHTTP_SECURE_FAILURE)
+                        {
+                            // https://stackoverflow.com/questions/19338395/how-do-you-use-winhttp-to-do-ssl-with-a-self-signed-cert
+                            DWORD dwFlags =
+                                SECURITY_FLAG_IGNORE_UNKNOWN_CA |
+                                SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
+                                SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+                                SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
+
+                            if (WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
+                                retry = true;
+                        }
+#endif
+                    }
+                } while (retry);
 
                 if (!bResults || !WinHttpReceiveResponse(hRequest, nullptr))
                 {
diff --git a/src/ui/viewmodels/AssetListViewModel.cpp b/src/ui/viewmodels/AssetListViewModel.cpp
index e760afde..636fc171 100644
--- a/src/ui/viewmodels/AssetListViewModel.cpp
+++ b/src/ui/viewmodels/AssetListViewModel.cpp
@@ -47,6 +47,7 @@ AssetListViewModel::AssetListViewModel() noexcept
     m_vStates.Add(ra::etoi(ra::data::models::AssetState::Active), L"Active");
     m_vStates.Add(ra::etoi(ra::data::models::AssetState::Paused), L"Paused");
     m_vStates.Add(ra::etoi(ra::data::models::AssetState::Triggered), L"Triggered");
+    m_vStates.Add(ra::etoi(ra::data::models::AssetState::Disabled), L"Disabled");
 
     m_vCategories.Add(ra::etoi(ra::data::models::AssetCategory::Core), L"Core");
     m_vCategories.Add(ra::etoi(ra::data::models::AssetCategory::Unofficial), L"Unofficial");
diff --git a/src/ui/viewmodels/MemoryViewerViewModel.cpp b/src/ui/viewmodels/MemoryViewerViewModel.cpp
index ee0fcd1c..1fec6150 100644
--- a/src/ui/viewmodels/MemoryViewerViewModel.cpp
+++ b/src/ui/viewmodels/MemoryViewerViewModel.cpp
@@ -86,7 +86,7 @@ private:
 
 MemoryViewerViewModel::MemoryViewerViewModel()
 {
-    m_pBuffer.reset(new uint8_t[MaxLines * 16 * 2]);
+    m_pBuffer.reset(new uint8_t[MaxLines * 16 * 3]);
 
     m_pMemory = m_pBuffer.get();
     memset(m_pMemory, 0, MaxLines * 16);
@@ -95,6 +95,9 @@ MemoryViewerViewModel::MemoryViewerViewModel()
     memset(m_pColor, STALE_COLOR, MaxLines * 16);
 
     m_pColor[0] = HIGHLIGHTED_COLOR;
+
+    m_pInvalid = m_pMemory + MaxLines * 16 * 2;
+    memset(m_pInvalid, 0, MaxLines * 16);
 }
 
 void MemoryViewerViewModel::InitializeNotifyTargets()
@@ -167,6 +170,13 @@ void MemoryViewerViewModel::UpdateColors()
     for (int i = 0; i < nVisibleLines * 16; ++i)
         m_pColor[i] = STALE_COLOR | gsl::narrow_cast<uint8_t>(ra::etoi(GetColor(nFirstAddress + i, pBookmarksViewModel, pGameContext)));
 
+    const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
+    if (pEmulatorContext.HasInvalidRegions())
+    {
+        for (int i = 0; i < nVisibleLines * 16; ++i)
+            m_pInvalid[i] = pEmulatorContext.IsValidAddress(nFirstAddress + i) ? 0 : 1;
+    }
+
     UpdateHighlight(GetAddress(), NibblesPerWord() / 2, 0);
 
     m_nNeedsRedraw |= REDRAW_MEMORY;
@@ -704,6 +714,12 @@ bool MemoryViewerViewModel::OnChar(char c)
         nSelectedNibble -= 2;
     }
 
+    if (m_pInvalid[nIndex])
+    {
+        AdvanceCursor();
+        return true;
+    }
+
     auto nByte = m_pMemory[nIndex];
     if (nSelectedNibble != 0)
     {
@@ -783,8 +799,8 @@ void MemoryViewerViewModel::DoFrame()
 
 // ====== Render ==============================================================
 
-static constexpr std::array<wchar_t, 16> g_sHexChars = {
-    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+static constexpr std::array<wchar_t, 17> g_sHexChars = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '-'
 };
 
 void MemoryViewerViewModel::BuildFontSurface()
@@ -798,7 +814,7 @@ void MemoryViewerViewModel::BuildFontSurface()
     m_szChar = pSurface->MeasureText(m_nFont, L"0");
     m_szChar.Height--; // don't need space for dangly bits
 
-    m_pFontSurface = pSurfaceFactory.CreateSurface(m_szChar.Width * 16, m_szChar.Height * ra::etoi(TextColor::NumColors));
+    m_pFontSurface = pSurfaceFactory.CreateSurface(m_szChar.Width * gsl::narrow_cast<int>(g_sHexChars.size()), m_szChar.Height * ra::etoi(TextColor::NumColors));
     m_pFontSurface->FillRectangle(0, 0, m_pFontSurface->GetWidth(), m_pFontSurface->GetHeight(), pEditorTheme.ColorBackground());
 
     m_pFontSurface->FillRectangle(0, m_szChar.Height * ra::etoi(TextColor::Cursor),
@@ -818,7 +834,7 @@ void MemoryViewerViewModel::BuildFontSurface()
             case TextColor::Frozen:       nColor = pEditorTheme.ColorFrozen(); break;
         }
 
-        for (int j = 0; j < 16; ++j)
+        for (int j = 0; j < gsl::narrow_cast<int>(g_sHexChars.size()); ++j)
         {
             sHexChar.at(0) = g_sHexChars.at(j);
             m_pFontSurface->WriteText(j * m_szChar.Width, i * m_szChar.Height - 1, m_nFont, nColor, sHexChar);
@@ -918,9 +934,18 @@ void MemoryViewerViewModel::RenderMemory()
             }
         }
 
-        const auto nValue = m_pMemory[i];
-        WriteChar(nX, nY, nColorUpper, nValue >> 4);
-        WriteChar(nX + m_szChar.Width, nY, nColorLower, nValue & 0x0F);
+        if (m_pInvalid[i])
+        {
+            constexpr int INVALID_CHAR = 16;
+            WriteChar(nX, nY, nColorUpper, INVALID_CHAR);
+            WriteChar(nX + m_szChar.Width, nY, nColorLower, INVALID_CHAR);
+        }
+        else
+        {
+            const auto nValue = m_pMemory[i];
+            WriteChar(nX, nY, nColorUpper, nValue >> 4);
+            WriteChar(nX + m_szChar.Width, nY, nColorLower, nValue & 0x0F);
+        }
     }
 }
 
diff --git a/src/ui/viewmodels/MemoryViewerViewModel.hh b/src/ui/viewmodels/MemoryViewerViewModel.hh
index 983db9b2..f8ad1f48 100644
--- a/src/ui/viewmodels/MemoryViewerViewModel.hh
+++ b/src/ui/viewmodels/MemoryViewerViewModel.hh
@@ -184,6 +184,7 @@ protected:
 
     uint8_t* m_pMemory;
     uint8_t* m_pColor;
+    uint8_t* m_pInvalid;
 
     ra::ui::Size m_szChar;
     int m_nSelectedNibble = 0;
diff --git a/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp b/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp
index b9fff281..64b375e1 100644
--- a/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp
+++ b/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp
@@ -84,6 +84,13 @@ static void SetAchievement(OverlayListPageViewModel::ItemViewModel& vmItem, cons
             vmItem.SetProgressValue(0);
         }
     }
+    else if (vmAchievement.GetState() == ra::data::models::AssetState::Disabled)
+    {
+        vmItem.Image.ChangeReference(ra::ui::ImageType::Badge, ra::Narrow(vmAchievement.GetBadge()) + "_lock");
+        vmItem.SetDisabled(true);
+        vmItem.SetProgressValue(0U);
+        vmItem.SetProgressMaximum(0U);
+    }
     else
     {
         vmItem.Image.ChangeReference(ra::ui::ImageType::Badge, ra::Narrow(vmAchievement.GetBadge()));
@@ -126,6 +133,7 @@ void OverlayAchievementsPageViewModel::Refresh()
     std::vector<const ra::data::models::AchievementModel*> vLocalAchievements;
     std::vector<const ra::data::models::AchievementModel*> vLockedCoreAchievements;
     std::vector<const ra::data::models::AchievementModel*> vUnlockedCoreAchievements;
+    std::vector<const ra::data::models::AchievementModel*> vUnsupportedCoreAchievements;
 
     const auto& pRuntime = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>();
     const auto tNow = ra::services::ServiceLocator::Get<ra::services::IClock>().Now();
@@ -188,6 +196,8 @@ void OverlayAchievementsPageViewModel::Refresh()
             default:
                 if (vmAchievement->IsActive())
                     vLockedCoreAchievements.push_back(vmAchievement);
+                else if (vmAchievement->GetState() == ra::data::models::AssetState::Disabled)
+                    vUnsupportedCoreAchievements.push_back(vmAchievement);
                 else
                     vUnlockedCoreAchievements.push_back(vmAchievement);
                 break;
@@ -300,6 +310,22 @@ void OverlayAchievementsPageViewModel::Refresh()
         nNumberOfCoreAchievements += vLockedCoreAchievements.size();
     }
 
+    if (!vUnsupportedCoreAchievements.empty())
+    {
+        auto& pvmHeader = GetNextItem(&nIndex);
+        SetHeader(pvmHeader, L"Unsupported");
+
+        for (const auto* vmAchievement : vUnsupportedCoreAchievements)
+        {
+            auto& pvmAchievement = GetNextItem(&nIndex);
+            SetAchievement(pvmAchievement, *vmAchievement);
+            nMaxPts += vmAchievement->GetPoints();
+        }
+
+        nNumberOfAchievements += vUnsupportedCoreAchievements.size();
+        nNumberOfCoreAchievements += vUnsupportedCoreAchievements.size();
+    }
+
     if (vUnlockedCoreAchievements.size() > 0)
     {
         if (nIndex > 0)


Test patch:
diff --git a/tests/data/DataAsserts.hh b/tests/data/DataAsserts.hh
index 1decf744..620f5ce6 100644
--- a/tests/data/DataAsserts.hh
+++ b/tests/data/DataAsserts.hh
@@ -94,6 +94,8 @@ std::wstring ToString<ra::data::models::AssetState>(
             return L"Waiting";
         case ra::data::models::AssetState::Paused:
             return L"Paused";
+        case ra::data::models::AssetState::Disabled:
+            return L"Disabled";
         default:
             return std::to_wstring(static_cast<int>(nAssetState));
     }
diff --git a/tests/data/context/EmulatorContext_Tests.cpp b/tests/data/context/EmulatorContext_Tests.cpp
index 4d15b948..50f5a8d5 100644
--- a/tests/data/context/EmulatorContext_Tests.cpp
+++ b/tests/data/context/EmulatorContext_Tests.cpp
@@ -4,6 +4,7 @@
 
 #include "tests\ui\UIAsserts.hh"
 
+#include "tests\mocks\MockAchievementRuntime.hh"
 #include "tests\mocks\MockClock.hh"
 #include "tests\mocks\MockConfiguration.hh"
 #include "tests\mocks\MockDesktop.hh"
@@ -32,6 +33,7 @@ private:
         ra::api::mocks::MockServer mockServer;
         ra::data::context::mocks::MockGameContext mockGameContext;
         ra::data::context::mocks::MockUserContext mockUserContext;
+        ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
         ra::services::mocks::MockClock mockClock;
         ra::services::mocks::MockConfiguration mockConfiguration;
         ra::services::mocks::MockFileSystem mockFileSystem;
@@ -921,10 +923,28 @@ public:
     static void WriteMemory2(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 20) = nValue; }
     static void WriteMemory3(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 30) = nValue; }
 
-    TEST_METHOD(TestReadMemoryByte)
+    static void InitializeMemory()
     {
         for (size_t i = 0; i < memory.size(); ++i)
             memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+    }
+
+    TEST_METHOD(TestIsValidAddress)
+    {
+        EmulatorContextHarness emulator;
+        emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
+        emulator.AddMemoryBlock(1, 10, &ReadMemory2, &WriteMemory2);
+        Assert::AreEqual({ 30U }, emulator.TotalMemorySize());
+
+        Assert::IsTrue(emulator.IsValidAddress(12U));
+        Assert::IsTrue(emulator.IsValidAddress(25U));
+        Assert::IsTrue(emulator.IsValidAddress(29U));
+        Assert::IsFalse(emulator.IsValidAddress(30U));
+    }
+
+    TEST_METHOD(TestReadMemoryByte)
+    {
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
@@ -940,8 +960,7 @@ public:
 
     TEST_METHOD(TestAddMemoryBlocksOutOfOrder)
     {
-        for (size_t i = 0; i < memory.size(); ++i)
-            memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(1, 10, &ReadMemory2, &WriteMemory2);
@@ -957,8 +976,7 @@ public:
 
     TEST_METHOD(TestAddMemoryBlockDoesNotOverwrite)
     {
-        for (size_t i = 0; i < memory.size(); ++i)
-            memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
@@ -975,8 +993,7 @@ public:
 
     TEST_METHOD(TestClearMemoryBlocks)
     {
-        for (size_t i = 0; i < memory.size(); ++i)
-            memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
@@ -990,6 +1007,33 @@ public:
         Assert::AreEqual(0, static_cast<int>(emulator.ReadMemoryByte(55U)));
     }
 
+    TEST_METHOD(TestInvalidMemoryBlock)
+    {
+        InitializeMemory();
+
+        EmulatorContextHarness emulator;
+        emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
+        emulator.AddMemoryBlock(1, 10, nullptr, nullptr);
+        emulator.AddMemoryBlock(2, 10, &ReadMemory3, &WriteMemory3);
+        Assert::AreEqual({ 40U }, emulator.TotalMemorySize());
+
+        Assert::IsTrue(emulator.IsValidAddress(12U));
+        Assert::IsTrue(emulator.IsValidAddress(19U));
+        Assert::IsFalse(emulator.IsValidAddress(20U));
+        Assert::IsFalse(emulator.IsValidAddress(29U));
+        Assert::IsTrue(emulator.IsValidAddress(30U));
+        Assert::IsTrue(emulator.IsValidAddress(39U));
+        Assert::IsFalse(emulator.IsValidAddress(40U));
+
+        Assert::AreEqual(12, static_cast<int>(emulator.ReadMemoryByte(12U)));
+        Assert::AreEqual(19, static_cast<int>(emulator.ReadMemoryByte(19U)));
+        Assert::AreEqual(0, static_cast<int>(emulator.ReadMemoryByte(20U)));
+        Assert::AreEqual(0, static_cast<int>(emulator.ReadMemoryByte(29U)));
+        Assert::AreEqual(30, static_cast<int>(emulator.ReadMemoryByte(30U)));
+        Assert::AreEqual(39, static_cast<int>(emulator.ReadMemoryByte(39U)));
+        Assert::AreEqual(0, static_cast<int>(emulator.ReadMemoryByte(40U)));
+    }
+
     TEST_METHOD(TestReadMemory)
     {
         memory.at(4) = 0xA8;
@@ -1032,8 +1076,7 @@ public:
 
     TEST_METHOD(TestReadMemoryBuffer)
     {
-        for (size_t i = 0; i < memory.size(); ++i)
-            memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
@@ -1076,8 +1119,7 @@ public:
 
     TEST_METHOD(TestWriteMemoryByte)
     {
-        for (size_t i = 0; i < memory.size(); ++i)
-            memory.at(i) = gsl::narrow_cast<uint8_t>(i);
+        InitializeMemory();
 
         EmulatorContextHarness emulator;
         emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp
index 0fba699c..ae1016fe 100644
--- a/tests/data/context/GameContext_Tests.cpp
+++ b/tests/data/context/GameContext_Tests.cpp
@@ -910,15 +910,15 @@ public:
         game.mockThreadPool.ExecuteNextTask(); // FetchUserUnlocks and FetchCodeNotes are async
         game.mockThreadPool.ExecuteNextTask();
 
-        const auto* pAch1 = game.FindAchievement(5U);
+        const auto* pAch1 = game.Assets().FindAchievement(5U);
         Assert::IsNotNull(pAch1);
         Ensures(pAch1 != nullptr);
-        Assert::IsTrue(pAch1->Active());
+        Assert::IsTrue(pAch1->IsActive());
 
-        const auto* pAch2 = game.FindAchievement(7U);
+        const auto* pAch2 = game.Assets().FindAchievement(7U);
         Assert::IsNotNull(pAch2);
         Ensures(pAch2 != nullptr);
-        Assert::IsFalse(pAch2->Active());
+        Assert::IsFalse(pAch2->IsActive());
     }
 
     TEST_METHOD(TestLoadGameUserUnlocksCompatibilityMode)
@@ -947,15 +947,15 @@ public:
         game.mockThreadPool.ExecuteNextTask(); // FetchUserUnlocks and FetchCodeNotes are async
         game.mockThreadPool.ExecuteNextTask();
 
-        const auto* pAch1 = game.FindAchievement(5U);
+        const auto* pAch1 = game.Assets().FindAchievement(5U);
         Assert::IsNotNull(pAch1);
         Ensures(pAch1 != nullptr);
-        Assert::IsTrue(pAch1->Active());
+        Assert::IsTrue(pAch1->IsActive());
 
-        const auto* pAch2 = game.FindAchievement(7U);
+        const auto* pAch2 = game.Assets().FindAchievement(7U);
         Assert::IsNotNull(pAch2);
         Ensures(pAch2 != nullptr);
-        Assert::IsTrue(pAch2->Active());
+        Assert::IsTrue(pAch2->IsActive());
     }
 
     TEST_METHOD(TestLoadGamePausesRuntime)
diff --git a/tests/services/AchievementRuntime_Tests.cpp b/tests/services/AchievementRuntime_Tests.cpp
index 0070c9fa..f25214ac 100644
--- a/tests/services/AchievementRuntime_Tests.cpp
+++ b/tests/services/AchievementRuntime_Tests.cpp
@@ -26,6 +26,18 @@ std::wstring ToString<ra::services::AchievementRuntime::ChangeType>(
             return L"AchievementTriggered";
         case ra::services::AchievementRuntime::ChangeType::AchievementReset:
             return L"AchievementReset";
+        case ra::services::AchievementRuntime::ChangeType::AchievementDisabled:
+            return L"AchievementDisabled";
+        case ra::services::AchievementRuntime::ChangeType::LeaderboardStarted:
+            return L"LeaderboardStarted";
+        case ra::services::AchievementRuntime::ChangeType::LeaderboardCanceled:
+            return L"LeaderboardCanceled";
+        case ra::services::AchievementRuntime::ChangeType::LeaderboardUpdated:
+            return L"LeaderboardUpdated";
+        case ra::services::AchievementRuntime::ChangeType::LeaderboardTriggered:
+            return L"LeaderboardTriggered";
+        case ra::services::AchievementRuntime::ChangeType::LeaderboardDisabled:
+            return L"LeaderboardDisabled";
         default:
             return std::to_wstring(static_cast<int>(nChangeType));
     }
@@ -118,6 +130,19 @@ private:
         return rc_parse_richpresence(sBuffer, sScript, nullptr, 0);
     }
 
+    static void AssertChange(const std::vector<AchievementRuntime::Change>& vChanges, AchievementRuntime::ChangeType nType, unsigned nId)
+    {
+        for (const auto& pChange : vChanges)
+        {
+            if (pChange.nId == nId && pChange.nType == nType)
+                return;
+        }
+
+        const auto pString = ra::StringPrintf(L"Could not find change notification for %s:%d",
+            Microsoft::VisualStudio::CppUnitTestFramework::ToString<AchievementRuntime::ChangeType>(nType), nId);
+        Assert::Fail(pString.c_str());
+    }
+
 public:
     TEST_METHOD(TestActivateAchievement)
     {
@@ -141,8 +166,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 6U);
 
         // triggered achievement should not continue to trigger
         vChanges.clear();
@@ -198,8 +222,7 @@ public:
         runtime.SetPaused(false);
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 6U);
     }
 
     TEST_METHOD(TestMonitorAchievementReset)
@@ -221,8 +244,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(4U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementReset, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementReset, 4U);
         vChanges.clear();
 
         // watch for notification, already reset, no notification
@@ -238,8 +260,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(4U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementReset, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementReset, 4U);
         vChanges.clear();
 
         // already reset, shouldn't get repeated notification
@@ -252,8 +273,7 @@ public:
         runtime.Process(vChanges);
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(4U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 4U);
         vChanges.clear();
     }
 
@@ -291,8 +311,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(99001U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 99001U);
     }
 
     TEST_METHOD(TestResetActiveAchievements)
@@ -345,10 +364,8 @@ public:
         memory.at(1) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 2U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
-        Assert::AreEqual(7U, vChanges.back().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementReset, vChanges.back().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 6U);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementReset, 7U);
         Assert::AreEqual(0U, pTrigger2->requirement->conditions->current_hits);
     }
 
@@ -668,10 +685,8 @@ public:
         std::vector<AchievementRuntime::Change> vChanges;
         runtime.Process(vChanges);
         Assert::AreEqual({ 2U }, vChanges.size());
-        Assert::AreEqual(pAchievement1.ID(), vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.front().nType);
-        Assert::AreEqual(pAchievement2.ID(), vChanges.at(1).nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::AchievementTriggered, vChanges.at(1).nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 1U);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::AchievementTriggered, 12U);
     }
 
     TEST_METHOD(TestReactivateAchievement)
@@ -786,8 +801,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardStarted, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardStarted, 6U);
         Assert::AreEqual(52, vChanges.front().nValue);
 
         // still active leaderboard should not notify unless the value changes
@@ -798,8 +812,7 @@ public:
         memory.at(2) = 33;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardUpdated, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardUpdated, 6U);
         Assert::AreEqual(33, vChanges.front().nValue);
         vChanges.clear();
 
@@ -807,8 +820,7 @@ public:
         memory.at(0) = 2;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardCanceled, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardCanceled, 6U);
         vChanges.clear();
 
         // leaderboard submit condition should not trigger if leaderboard not started
@@ -821,8 +833,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardStarted, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardStarted, 6U);
         Assert::AreEqual(33, vChanges.front().nValue);
         vChanges.clear();
 
@@ -830,8 +841,7 @@ public:
         memory.at(0) = 3;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardTriggered, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardTriggered, 6U);
         Assert::AreEqual(33, vChanges.front().nValue);
         vChanges.clear();
 
@@ -844,8 +854,7 @@ public:
         memory.at(0) = 1;
         runtime.Process(vChanges);
         Assert::AreEqual({ 1U }, vChanges.size());
-        Assert::AreEqual(6U, vChanges.front().nId);
-        Assert::AreEqual(AchievementRuntime::ChangeType::LeaderboardStarted, vChanges.front().nType);
+        AssertChange(vChanges, AchievementRuntime::ChangeType::LeaderboardStarted, 6U);
         Assert::AreEqual(33, vChanges.front().nValue);
         vChanges.clear();
 
diff --git a/tests/services/JsonFileConfiguration_Tests.cpp b/tests/services/JsonFileConfiguration_Tests.cpp
index 0a9823d4..00007b31 100644
--- a/tests/services/JsonFileConfiguration_Tests.cpp
+++ b/tests/services/JsonFileConfiguration_Tests.cpp
@@ -239,7 +239,7 @@ public:
         MockFileSystem mockFileSystem;
         JsonFileConfiguration config;
         Assert::AreEqual(std::string("retroachievements.org"), config.GetHostName());
-        Assert::AreEqual(std::string("http://retroachievements.org"), config.GetHostUrl());
+        Assert::AreEqual(std::string("https://retroachievements.org"), config.GetHostUrl());
         Assert::AreEqual(std::string("http://i.retroachievements.org"), config.GetImageHostUrl());
     }
 
diff --git a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
index 124b893b..f014e1fa 100644
--- a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
@@ -381,7 +381,7 @@ public:
         Assert::AreEqual(0, vmAssetList.GetTotalPoints());
         Assert::AreEqual(true, vmAssetList.IsProcessingActive());
 
-        Assert::AreEqual({ 5U }, vmAssetList.States().Count());
+        Assert::AreEqual({ 6U }, vmAssetList.States().Count());
         Assert::AreEqual((int)AssetState::Inactive, vmAssetList.States().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L"Inactive"), vmAssetList.States().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)AssetState::Waiting, vmAssetList.States().GetItemAt(1)->GetId());
@@ -392,6 +392,8 @@ public:
         Assert::AreEqual(std::wstring(L"Paused"), vmAssetList.States().GetItemAt(3)->GetLabel());
         Assert::AreEqual((int)AssetState::Triggered, vmAssetList.States().GetItemAt(4)->GetId());
         Assert::AreEqual(std::wstring(L"Triggered"), vmAssetList.States().GetItemAt(4)->GetLabel());
+        Assert::AreEqual((int)AssetState::Disabled, vmAssetList.States().GetItemAt(5)->GetId());
+        Assert::AreEqual(std::wstring(L"Disabled"), vmAssetList.States().GetItemAt(5)->GetLabel());
 
         Assert::AreEqual({ 3U }, vmAssetList.Categories().Count());
         Assert::AreEqual((int)AssetCategory::Core, vmAssetList.Categories().GetItemAt(0)->GetId());
diff --git a/tests/ui/viewmodels/CodeNotesViewModel_Tests.cpp b/tests/ui/viewmodels/CodeNotesViewModel_Tests.cpp
index dd05eff7..6f3ae08d 100644
--- a/tests/ui/viewmodels/CodeNotesViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/CodeNotesViewModel_Tests.cpp
@@ -51,6 +51,14 @@ private:
 
             mockGameContext.SetCodeNote(0x0040, L"[10 bytes] Inventory");
         }
+
+        void PopulateMemory()
+        {
+            std::array<uint8_t, 100> memory = {};
+            for (uint8_t i = 0; i < memory.size(); ++i)
+                memory.at(i) = i;
+            mockEmulatorContext.MockMemory(memory);
+        }
     };
 
     void AssertRow(CodeNotesViewModelHarness& notes, gsl::index nRow, ra::ByteAddress nAddress,
@@ -408,6 +416,7 @@ public:
     {
         CodeNotesViewModelHarness notes;
         notes.PopulateNotes();
+        notes.PopulateMemory();
         Assert::AreEqual({ 0U }, notes.Notes().Count());
 
         notes.SetIsVisible(true);
diff --git a/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp b/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
index 8b20f319..23ad2a81 100644
--- a/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/MemoryBookmarksViewModel_Tests.cpp
@@ -5,6 +5,7 @@
 
 #include "tests\ui\UIAsserts.hh"
 
+#include "tests\mocks\MockAchievementRuntime.hh"
 #include "tests\mocks\MockConfiguration.hh"
 #include "tests\mocks\MockDesktop.hh"
 #include "tests\mocks\MockEmulatorContext.hh"
@@ -54,6 +55,7 @@ private:
     public:
         ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
         ra::data::context::mocks::MockGameContext mockGameContext;
+        ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
         ra::services::mocks::MockConfiguration mockConfiguration;
         ra::services::mocks::MockFileSystem mockFileSystem;
         ra::services::mocks::MockFrameEventQueue mockFrameEventQueue;
diff --git a/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp b/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp
index 79d68864..9e77651b 100644
--- a/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp
@@ -223,6 +223,156 @@ public:
         Assert::IsNull(achievementsPage.GetItem(5));
     }
 
+    TEST_METHOD(TestRefreshActiveAndDisabledAchievements)
+    {
+        OverlayAchievementsPageViewModelHarness achievementsPage;
+        auto& pAch1 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch1.SetID(1);
+        pAch1.SetPoints(1U);
+        pAch1.SetState(AssetState::Waiting);
+        auto& pAch2 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch2.SetID(2);
+        pAch2.SetPoints(2U);
+        pAch2.SetState(AssetState::Disabled);
+        auto& pAch3 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch3.SetID(3);
+        pAch3.SetPoints(3U);
+        pAch3.SetState(AssetState::Active);
+        auto& pAch4 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch4.SetID(4);
+        pAch4.SetPoints(4U);
+        pAch4.SetState(AssetState::Disabled);
+        achievementsPage.mockClock.AdvanceTime(std::chrono::hours(1));
+        achievementsPage.Refresh();
+
+        Assert::AreEqual(std::wstring(L"Achievements"), achievementsPage.GetTitle());
+        Assert::AreEqual(std::wstring(L"0 of 4 won (0/10)"), achievementsPage.GetSummary());
+
+        auto const* pItem = achievementsPage.GetItem(0);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(1, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(1);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(3, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(2);
+        Expects(pItem != nullptr);
+        Assert::IsTrue(pItem->IsHeader());
+        Assert::AreEqual(std::wstring(L"Unsupported"), pItem->GetLabel());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(3);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(2, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(4);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(4, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        Assert::IsNull(achievementsPage.GetItem(5));
+    }
+
+    TEST_METHOD(TestRefreshInactiveAndDisabledAchievements)
+    {
+        OverlayAchievementsPageViewModelHarness achievementsPage;
+        auto& pAch1 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch1.SetID(1);
+        pAch1.SetPoints(1U);
+        pAch1.SetState(AssetState::Inactive);
+        auto& pAch2 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch2.SetID(2);
+        pAch2.SetPoints(2U);
+        pAch2.SetState(AssetState::Disabled);
+        auto& pAch3 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch3.SetID(3);
+        pAch3.SetPoints(3U);
+        pAch3.SetState(AssetState::Triggered);
+        auto& pAch4 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch4.SetID(4);
+        pAch4.SetPoints(4U);
+        pAch4.SetState(AssetState::Disabled);
+        achievementsPage.mockClock.AdvanceTime(std::chrono::hours(1));
+        achievementsPage.Refresh();
+
+        Assert::AreEqual(std::wstring(L"Achievements"), achievementsPage.GetTitle());
+        Assert::AreEqual(std::wstring(L"2 of 4 won (4/10)"), achievementsPage.GetSummary());
+
+        auto const* pItem = achievementsPage.GetItem(0);
+        Expects(pItem != nullptr);
+        Assert::IsTrue(pItem->IsHeader());
+        Assert::AreEqual(std::wstring(L"Unsupported"), pItem->GetLabel());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(1);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(2, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(2);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(4, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(3);
+        Expects(pItem != nullptr);
+        Assert::IsTrue(pItem->IsHeader());
+        Assert::AreEqual(std::wstring(L"Unlocked"), pItem->GetLabel());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(4);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(1, pItem->GetId());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(5);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(3, pItem->GetId());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        Assert::IsNull(achievementsPage.GetItem(6));
+    }
+
+    TEST_METHOD(TestRefreshDisabledAchievements)
+    {
+        OverlayAchievementsPageViewModelHarness achievementsPage;
+        auto& pAch2 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch2.SetID(2);
+        pAch2.SetPoints(2U);
+        pAch2.SetState(AssetState::Disabled);
+        auto& pAch4 = achievementsPage.NewAchievement(AssetCategory::Core);
+        pAch4.SetID(4);
+        pAch4.SetPoints(4U);
+        pAch4.SetState(AssetState::Disabled);
+        achievementsPage.mockClock.AdvanceTime(std::chrono::hours(1));
+        achievementsPage.Refresh();
+
+        Assert::AreEqual(std::wstring(L"Achievements"), achievementsPage.GetTitle());
+        Assert::AreEqual(std::wstring(L"0 of 2 won (0/6)"), achievementsPage.GetSummary());
+
+        auto const* pItem = achievementsPage.GetItem(0);
+        Expects(pItem != nullptr);
+        Assert::IsTrue(pItem->IsHeader());
+        Assert::AreEqual(std::wstring(L"Unsupported"), pItem->GetLabel());
+        Assert::IsFalse(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(1);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(2, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        pItem = achievementsPage.GetItem(2);
+        Expects(pItem != nullptr);
+        Assert::AreEqual(4, pItem->GetId());
+        Assert::IsTrue(pItem->IsDisabled());
+
+        Assert::IsNull(achievementsPage.GetItem(3));
+    }
+
     TEST_METHOD(TestRefreshCategoryFilter)
     {
         OverlayAchievementsPageViewModelHarness achievementsPage;