From c1c82eb3f30e2eea66e1ed7e615f0ae4f998eff2 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Thu, 30 Jan 2020 15:50:14 +1000
Subject: [PATCH] Common/CDImage: Support CHD format

---
 src/common/cd_image.h             |   1 +
 src/common/cd_image_chd.cpp       | 327 ++++++++++++++++++++++++++++++
 src/common/common.vcxproj         |  20 +-
 src/common/common.vcxproj.filters |   1 +
 4 files changed, 341 insertions(+), 8 deletions(-)
 create mode 100644 src/common/cd_image_chd.cpp

diff --git a/src/common/cd_image.h b/src/common/cd_image.h
index 707cafc46..fa7e8ef5c 100644
--- a/src/common/cd_image.h
+++ b/src/common/cd_image.h
@@ -159,6 +159,7 @@ public:
   static std::unique_ptr<CDImage> Open(const char* filename);
   static std::unique_ptr<CDImage> OpenBinImage(const char* filename);
   static std::unique_ptr<CDImage> OpenCueSheetImage(const char* filename);
+  static std::unique_ptr<CDImage> OpenCHDImage(const char* filename);
 
   // Accessors.
   const std::string& GetFileName() const { return m_filename; }
diff --git a/src/common/cd_image_chd.cpp b/src/common/cd_image_chd.cpp
new file mode 100644
index 000000000..3b4cb89eb
--- /dev/null
+++ b/src/common/cd_image_chd.cpp
@@ -0,0 +1,327 @@
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "assert.h"
+#include "cd_image.h"
+#include "cd_subchannel_replacement.h"
+#include "cpu_detect.h"
+#include "file_system.h"
+#include "libchdr/chd.h"
+#include "log.h"
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <optional>
+Log_SetChannel(CDImageCHD);
+
+static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str)
+{
+  if (std::strncmp(str, "MODE2_FORM_MIX", 14) == 0)
+    return CDImage::TrackMode::Mode2FormMix;
+  else if (std::strncmp(str, "MODE2_FORM1", 10) == 0)
+    return CDImage::TrackMode::Mode2Form1;
+  else if (std::strncmp(str, "MODE2_FORM2", 10) == 0)
+    return CDImage::TrackMode::Mode2Form2;
+  else if (std::strncmp(str, "MODE2_RAW", 9) == 0)
+    return CDImage::TrackMode::Mode2Raw;
+  else if (std::strncmp(str, "MODE1_RAW", 9) == 0)
+    return CDImage::TrackMode::Mode1Raw;
+  else if (std::strncmp(str, "MODE1", 5) == 0)
+    return CDImage::TrackMode::Mode1;
+  else if (std::strncmp(str, "MODE2", 5) == 0)
+    return CDImage::TrackMode::Mode2;
+  else if (std::strncmp(str, "AUDIO", 5) == 0)
+    return CDImage::TrackMode::Audio;
+  else
+    return std::nullopt;
+}
+
+class CDImageCHD : public CDImage
+{
+public:
+  CDImageCHD();
+  ~CDImageCHD() override;
+
+  bool Open(const char* filename);
+
+  bool ReadSubChannelQ(SubChannelQ* subq) override;
+
+protected:
+  bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
+
+private:
+  enum : u32
+  {
+    CHD_SECTOR_DATA_SIZE = 2352 + 96,
+  };
+
+  bool ReadHunk(u32 hunk_index);
+
+  chd_file* m_chd = nullptr;
+  u32 m_hunk_size = 0;
+  u32 m_sectors_per_hunk = 0;
+
+  std::vector<u8> m_hunk_buffer;
+  u32 m_current_hunk_index = static_cast<u32>(-1);
+
+  CDSubChannelReplacement m_sbi;
+};
+
+CDImageCHD::CDImageCHD() = default;
+
+CDImageCHD::~CDImageCHD()
+{
+  if (m_chd)
+    chd_close(m_chd);
+}
+
+bool CDImageCHD::Open(const char* filename)
+{
+  chd_error err = chd_open(filename, CHD_OPEN_READ, nullptr, &m_chd);
+  if (err != CHDERR_NONE)
+  {
+    Log_ErrorPrintf("Failed to open CHD '%s': %s", chd_error_string(err));
+    return false;
+  }
+
+  const chd_header* header = chd_get_header(m_chd);
+  m_hunk_size = header->hunkbytes;
+  if ((m_hunk_size % CHD_SECTOR_DATA_SIZE) != 0)
+  {
+    Log_ErrorPrintf("Hunk size (%u) is not a multiple of %u", m_hunk_size, CHD_SECTOR_DATA_SIZE);
+    return false;
+  }
+
+  m_sectors_per_hunk = m_hunk_size / CHD_SECTOR_DATA_SIZE;
+  m_hunk_buffer.resize(m_hunk_size);
+  m_filename = filename;
+
+  u32 disc_lba = 0;
+  u64 disc_frame = 0;
+
+  // "last track" subchannel q - used for the pregap
+  SubChannelQ::Control last_track_control{};
+
+  // for each track..
+  int num_tracks = 0;
+  for (;;)
+  {
+    char metadata_str[256];
+    char type_str[256];
+    char subtype_str[256];
+    char pgtype_str[256];
+    char pgsub_str[256];
+    u32 metadata_length;
+
+    int track_num = 0, frames = 0, pad = 0, pregap_frames = 0, postgap_frames = 0;
+    err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str),
+                           &metadata_length, nullptr, nullptr);
+    if (err == CHDERR_NONE)
+    {
+      if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames,
+                      &pregap_frames, pgtype_str, pgsub_str, &postgap_frames) != 8)
+      {
+        Log_ErrorPrintf("Invalid track v2 metadata: '%s'", metadata_str);
+        return false;
+      }
+    }
+    else
+    {
+      // try old version
+      err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA_TAG, num_tracks, metadata_str, sizeof(metadata_str),
+                             &metadata_length, nullptr, nullptr);
+      if (err != CHDERR_NONE)
+      {
+        // not found, so no more tracks
+        break;
+      }
+
+      if (std::sscanf(metadata_str, CDROM_TRACK_METADATA_FORMAT, &track_num, type_str, subtype_str, &frames) != 4)
+      {
+        Log_ErrorPrintf("Invalid track metadata: '%s'", metadata_str);
+        return false;
+      }
+    }
+
+    if (track_num != (num_tracks + 1))
+    {
+      Log_ErrorPrintf("Incorrect track number at index %d, expected %d got %d", num_tracks, (num_tracks + 1),
+                      track_num);
+      return false;
+    }
+
+    std::optional<TrackMode> mode = ParseTrackModeString(type_str);
+    if (!mode.has_value())
+    {
+      Log_ErrorPrintf("Invalid track mode: '%s'", type_str);
+      return false;
+    }
+
+    // precompute subchannel q flags for the whole track
+    SubChannelQ::Control control{};
+    control.data = mode.value() != TrackMode::Audio;
+
+    // two seconds pregap for track 1 is assumed if not specified
+    const bool pregap_in_file = (pregap_frames > 0 && pgtype_str[0] == 'V');
+    if (pregap_frames <= 0 && mode != TrackMode::Audio)
+      pregap_frames = 2 * FRAMES_PER_SECOND;
+
+    // create the index for the pregap
+    if (pregap_frames > 0)
+    {
+      Index pregap_index = {};
+      pregap_index.start_lba_on_disc = disc_lba;
+      pregap_index.start_lba_in_track = static_cast<LBA>(static_cast<unsigned long>(-pregap_frames));
+      pregap_index.length = pregap_frames;
+      pregap_index.track_number = track_num;
+      pregap_index.index_number = 0;
+      pregap_index.mode = mode.value();
+      pregap_index.control.bits = (track_num > 1) ? last_track_control.bits : control.bits;
+      pregap_index.is_pregap = true;
+
+      if (pregap_in_file)
+      {
+        if (pregap_frames > frames)
+        {
+          Log_ErrorPrintf("Pregap length %u exceeds track length %u", pregap_frames, frames);
+          return false;
+        }
+
+        pregap_index.file_index = 0;
+        pregap_index.file_offset = disc_lba;
+        pregap_index.file_sector_size = CHD_SECTOR_DATA_SIZE;
+        disc_frame += pregap_frames;
+        frames -= pregap_frames;
+      }
+
+      m_indices.push_back(pregap_index);
+      disc_lba += pregap_frames;
+    }
+
+    // add the track itself
+    m_tracks.push_back(Track{static_cast<u32>(track_num), disc_lba, static_cast<u32>(m_indices.size()),
+                             static_cast<u32>(frames), mode.value(), control});
+    last_track_control.bits = control.bits;
+
+    // how many indices in this track?
+    Index index = {};
+    index.start_lba_on_disc = disc_lba;
+    index.start_lba_in_track = 0;
+    index.track_number = track_num;
+    index.index_number = 1;
+    index.file_index = 0;
+    index.file_sector_size = CHD_SECTOR_DATA_SIZE;
+    index.file_offset = disc_frame;
+    index.mode = mode.value();
+    index.control.bits = control.bits;
+    index.is_pregap = false;
+    index.length = static_cast<u32>(frames);
+    m_indices.push_back(index);
+
+    disc_lba += index.length;
+    disc_frame += index.length;
+    num_tracks++;
+  }
+
+  m_lba_count = disc_lba;
+
+  m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
+
+  return Seek(1, Position{0, 0, 0});
+}
+
+bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq)
+{
+  if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq->data))
+    return true;
+
+  // TODO: Read subchannel data from CHD
+
+  return CDImage::ReadSubChannelQ(subq);
+}
+
+// There's probably a more efficient way of doing this with vectorization...
+ALWAYS_INLINE static void CopyAndSwap(void* dst_ptr, const u8* src_ptr, u32 data_size)
+{
+  u8* dst_ptr_byte = static_cast<u8*>(dst_ptr);
+#if defined(CPU_X64) || defined(CPU_AARCH64)
+  const u32 num_values = data_size / 8;
+  for (u32 i = 0; i < num_values; i++)
+  {
+    u64 value;
+    std::memcpy(&value, src_ptr, sizeof(value));
+    value = ((value >> 8) & UINT64_C(0x00FF00FF00FF00FF)) | ((value << 8) & UINT64_C(0xFF00FF00FF00FF00));
+    std::memcpy(dst_ptr_byte, &value, sizeof(value));
+    src_ptr += sizeof(value);
+    dst_ptr_byte += sizeof(value);
+  }
+#elif defined(CPU_X86) || defined(CPU_ARM)
+  const u32 num_values = data_size / 4;
+  for (u32 i = 0; i < num_values; i++)
+  {
+    u32 value;
+    std::memcpy(&value, src_ptr, sizeof(value));
+    value = ((value >> 8) & UINT32_C(0x00FF00FF)) | ((value << 8) & UINT32_C(0xFF00FF00));
+    std::memcpy(dst_ptr_byte, &value, sizeof(value));
+    src_ptr += sizeof(value);
+    dst_ptr_byte += sizeof(value);
+  }
+#else
+  const u32 num_values = data_size / sizeof(u16);
+  for (u32 i = 0; i < num_values; i++)
+  {
+    u16 value;
+    std::memcpy(&value, src_ptr, sizeof(value));
+    value = (value << 8) | (value >> 8);
+    std::memcpy(dst_ptr_byte, &value, sizeof(value));
+    src_ptr += sizeof(value);
+    dst_ptr_byte += sizeof(value);
+  }
+#endif
+}
+
+bool CDImageCHD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
+{
+  const u32 disc_frame = static_cast<LBA>(index.file_offset) + lba_in_index;
+  const u32 hunk_index = static_cast<u32>(disc_frame / m_sectors_per_hunk);
+  const u32 hunk_offset = static_cast<u32>((disc_frame % m_sectors_per_hunk) * CHD_SECTOR_DATA_SIZE);
+  DebugAssert((m_hunk_size - hunk_offset) >= CHD_SECTOR_DATA_SIZE);
+
+  if (m_current_hunk_index != hunk_index && !ReadHunk(hunk_index))
+    return false;
+
+  // Audio data is in big-endian, so we have to swap it for little endian hosts...
+  if (index.mode == TrackMode::Audio)
+    CopyAndSwap(buffer, &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE);
+  else
+    std::memcpy(buffer, &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE);
+
+  return true;
+}
+
+bool CDImageCHD::ReadHunk(u32 hunk_index)
+{
+  const chd_error err = chd_read(m_chd, hunk_index, m_hunk_buffer.data());
+  if (err != CHDERR_NONE)
+  {
+    Log_ErrorPrintf("chd_read(%u) failed: %s", hunk_index, chd_error_string(err));
+
+    // data might have been partially written
+    m_current_hunk_index = static_cast<u32>(-1);
+    return false;
+  }
+
+  m_current_hunk_index = hunk_index;
+  return true;
+}
+
+std::unique_ptr<CDImage> CDImage::OpenCHDImage(const char* filename)
+{
+  std::unique_ptr<CDImageCHD> image = std::make_unique<CDImageCHD>();
+  if (!image->Open(filename))
+    return {};
+
+  return image;
+}
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index 5836429b3..8a993f302 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -76,6 +76,7 @@
     <ClCompile Include="byte_stream.cpp" />
     <ClCompile Include="cd_image.cpp" />
     <ClCompile Include="cd_image_bin.cpp" />
+    <ClCompile Include="cd_image_chd.cpp" />
     <ClCompile Include="cd_image_cue.cpp" />
     <ClCompile Include="cubeb_audio_stream.cpp" />
     <ClCompile Include="d3d11\shader_cache.cpp" />
@@ -110,6 +111,9 @@
     <ProjectReference Include="..\..\dep\glad\glad.vcxproj">
       <Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
     </ProjectReference>
+    <ProjectReference Include="..\..\dep\libchdr\libchdr.vcxproj">
+      <Project>{425d6c99-d1c8-43c2-b8ac-4d7b1d941017}</Project>
+    </ProjectReference>
     <ProjectReference Include="..\..\dep\libcue\libcue.vcxproj">
       <Project>{6a4208ed-e3dc-41e1-81cd-f61025fc285a}</Project>
     </ProjectReference>
@@ -251,7 +255,7 @@
       <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
       <ConformanceMode>true</ConformanceMode>
@@ -277,7 +281,7 @@
       <PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <BasicRuntimeChecks>Default</BasicRuntimeChecks>
       <SupportJustMyCode>false</SupportJustMyCode>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -306,7 +310,7 @@
       <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
       <ConformanceMode>true</ConformanceMode>
@@ -332,7 +336,7 @@
       <PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <BasicRuntimeChecks>Default</BasicRuntimeChecks>
       <SupportJustMyCode>false</SupportJustMyCode>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -362,7 +366,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
       <WholeProgramOptimization>false</WholeProgramOptimization>
@@ -392,7 +396,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <OmitFramePointers>true</OmitFramePointers>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
@@ -422,7 +426,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
       <WholeProgramOptimization>false</WholeProgramOptimization>
@@ -452,7 +456,7 @@
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <OmitFramePointers>true</OmitFramePointers>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <LanguageStandard>stdcpp17</LanguageStandard>
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index bb91af0fc..84877fa9d 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -97,6 +97,7 @@
     <ClCompile Include="d3d11\shader_cache.cpp">
       <Filter>d3d11</Filter>
     </ClCompile>
+    <ClCompile Include="cd_image_chd.cpp" />
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="bitfield.natvis" />