Часть 1: Обход защиты

Однажды меня попросили прочитать файлы с расширением .xcm программы холтер-мониторинга и вывести из них кардиограмму на график. Всё бы ничего, но формат файлов оказался кастомным и не подходил под стандарты ни одним байтом. Без оригинальной программы разобраться в том, как их читать, было невозможно.

Я попросил прислать мне саму программу, но мне ответили, что без аппаратного ключа она не работает. Ключ при этом находится в Бразилии, и прислать его не могут, так как он нужен медикам для работы. «Присылайте так, разберусь», — сказал я. Была мысль глянуть, что там да как статически, а если получится — заставить её работать без ключа и смотреть уже в динамике.

Программа, к слову, называется CardioSmart фирмы Cardios и выглядит так:

Программа CardioSmart
Программа CardioSmart

Она написана на C++ с использованием MFC и представляет собой MDI-приложение (может работать с несколькими документами одновременно).

Первые находки

Ладно, довольно ли��ики, переходим к самому интересному. Когда я заглянул в каталог приложения, то увидел кучу DLL-библиотек с интересными названиями. Это явно указывало на то, что код писался с использованием принципов «чистой архитектуры», с разделением ответственности даже на бинарном уровне! Всё как завещал дядюшка Боб. То есть это были не просто библиотеки, а четко выделенные модули программы.

Также в директории я обнаружил файлы с расширением .cmm. По сути, это те же самые DLL-библиотеки, но с измененным расширением — видимо, чтобы скрыть их истинную природу от посторонних глаз. Они тоже являются полноценными модулями программы.

Диалог License status

При запуске программы появляется диалог ввода серийного номера. Нужно поискать по модулям, где он хранится, найти, откуда он вызывается. Кто-то скажет: «У тебя же наверняка есть модули с названиями license.dll, LicenseManager.dll и т.д.». Только вот подобных названий нет, поэтому придется искать. Бегло глянув ресурсы с помощью PE Explorer (программка старая, но выручает иногда), я обнаружил, что искомый диалог находится в AppsBase.dll. Зная ID диалога (24080 в десятичном виде или 5E10h в шестнадцатеричном), я загрузил AppsBase.dll в IDA Pro. Поиск по константе вывел на место вызова:

Конструктор CdlgLicenseStatus
CdlgLicenseStatus *__thiscall CdlgLicenseStatus::CdlgLicenseStatus(
        CdlgLicenseStatus *this,
        struct ChlpLicenseManager *a2,
        struct CWnd *a3)
{
  CdlgLicenseStatus *result; // eax

  CDialog::CDialog(this, 0x5E10u, a3);
  *(_DWORD *)this = &CdlgLicenseStatus::`vftable';
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 168);
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 172);
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 180);
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>((char *)this + 184);
  *((_DWORD *)this + 48) = a2;
  *(_QWORD *)((char *)this + 196) = 0i64;
  *((_DWORD *)this + 51) = 0;
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
    (char *)this + 168,
    &word_58A186F8);
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
    (char *)this + 172,
    &word_58A186F8);
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
    (char *)this + 180,
    &word_58A186F8);
  result = this;
  *((_BYTE *)this + 188) = 0;
  *((_DWORD *)this + 44) = 0;
  return result;
}

Это конструктор класса CdlgLicenseStatus. Сам класс и является тем самым диалогом активации. Интерес вызвал обработчик нажатия кнопки "Activate" — стандартный для таких случаев метод OnOK:

void __thiscall CdlgLicenseStatus::OnOK(const wchar_t **this)
{
  if ( CWnd::UpdateData((CWnd *)this, 1) )
  {
    CdlgLicenseStatus::WriteLicense(this);
    CdlgLicenseStatus::RefreshLicense((ChlpLicenseManager **)this);
    CDialog::EndDialog((CDialog *)this, 1);
  }
}

Метод WriteLicense, судя по всему, записывает лицензию в донгл (аппаратный ключ). Реализовано это через вызов метода WriteSerialNumberToReg, который, судя по названию, пишет серийный номер в реестр.

void __thiscall CdlgLicenseStatus::WriteLicense(const wchar_t **this)
{
  if ( *((_DWORD *)this[45] - 3) )
  {
    CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Fallback URL", this[45]);
  }
  else if ( this[44] )
  {
    CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Fallback URL", this[42]);
    if ( !*((_DWORD *)this[42] - 3) )
      ChlpCustomerSubscription::ClearSubscription();
  }
  CtypCfgConnEDongle::WriteSerialNumberToReg(L"License", L"Profile06", &word_58A186F8);
}

Модуль MfcEx.dll

Класс CtypCfgConnEDongle находится в модуле MfcEx.dll. Что ж посмотрим, что же это за метод такой WriteSerialNumberToReg.

Метод WriteSerialNumberToReg:
int __cdecl CtypCfgConnEDongle::WriteSerialNumberToReg(const wchar_t *a1, const wchar_t *a2, wchar_t *a3)
{
  struct AFX_MODULE_STATE *ModuleState; // eax
  int v4; // esi
  int v6; // [esp+0h] [ebp-F0h] BYREF
  char v7[8]; // [esp+D4h] [ebp-1Ch] BYREF
  int v8[2]; // [esp+DCh] [ebp-14h] BYREF
  int v9; // [esp+ECh] [ebp-4h]

  v8[1] = (int)&v6;
  v9 = 0;
  sub_10614C00(1);
  LOBYTE(v9) = 1;
  sub_10614D60((int)v8, a3);
  LOBYTE(v9) = 2;
  ModuleState = AfxGetModuleState();
  v4 = (*(int (__thiscall **)(_DWORD, const wchar_t *, const wchar_t *, int))(**((_DWORD **)ModuleState + 1) + 144))(
         *((_DWORD *)ModuleState + 1),
         a1,
         a2,
         v8[0]);
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v8);
  sub_10614C20(v7);
  return v4;
}

В модуле MfcEx.dll нет вызовов RegWrite и подобных. Если продолжать отлаживать это в лоб, можно просто утонуть в абстракциях и потратить кучу време��и впустую. К счастью, есть программа ProcessMonitor, и она показала, что серийный номер пишется вовсе не в реестр, а в ini файл C:\ProgramData\Cardios\CardioMg\CardioMg.ini. А еще что ведутся интересные логи CardioMg.0.log и Dongle.00.0.log.

Логи CardioMg.0.log и Dongle.00.0.log

CardioMg.0.log:

2026-02-05 16:10:38 <MAIN:INFO > {ModulesBase.dll::CappMainBase::PreInitInstance @408}: _______________________START__________________________
2026-02-05 16:10:38 <MAIN:INFO > {ModulesBase.dll::CappMainBase::PreInitInstance @409}: InitInstance: version 7.5.4.0
2026-02-05 16:10:39 <MAIN:WARN > {CardioLoop.cmm::DllMain @39}: License not found for CardioLoop Module.
2026-02-05 16:10:39 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'CardioLoop.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114.
2026-02-05 16:10:40 <MAIN:WARN > {CardioNetMod.cmm::DllMain @45}: License not found for CardioNet Module.
2026-02-05 16:10:40 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'CardioNetMod.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114.
2026-02-05 16:10:40 <MAIN:WARN > {Event.cmm::DllMain @43}: License not found for Event Module.
2026-02-05 16:10:40 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'Event.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114.
2026-02-05 16:10:41 <MAIN:WARN > {Monitor.cmm::DllMain @42}: License not found for Monitor Module.
2026-02-05 16:10:41 <MAIN:ERROR> {MultiCom.dll::CappMain::LoadModules @867}: The 'Monitor.cmm' library, located on the CardioManager® folder could not be loaded. Windows returned error code: 1114.
2026-02-05 16:10:46 <MAIN:WARN > {ModulesBase.dll::CappMainBase::CheckLicenseState @849}: InitInstance: No license found
2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @74}: ldap_init succeeded
2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @81}: ldap_set_option succeeded - version set to 3
2026-02-05 16:10:46 <MAIN:INFO > {AppsBase.dll::CappLicensed::UserLoginLdap @106}: ldap_connect failed with 0x51.
2026-02-05 16:10:46 <MAIN:WARN > {MultiCom.dll::CappMain::AuthenticateUser @1835}: Skipping user authentication: No Ldap connection or users data
2026-02-05 16:10:46 <MAIN:INFO > {ModulesBase.dll::CappMainBase::CheckSwUpdate @897}: Checking software update
2026-02-05 16:10:46 <MAIN:INFO > {MultiCom.dll::CappMain::InitInstance @671}: Application CardioManager® started

Dongle.00.0.log:

10240609	F:\CSLibs\MfcEx\devDongleWeb.cpp   176: Dongle ID matches!
10241171	F:\CSLibs\MfcEx\devDongleWeb.cpp   587: Session ID: '77Kfc46acb4b4q1ab95d9cad022c7fea' failed with error code 1
Description: ""10241171	F:\CSLibs\MfcEx\hlpDongle.cpp   235: Dumping buf Buffer
eJwzMFAwGEWjaBSNolFEX+Tmhg+ZmoFIAOmn8fA=
10241171	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   182: m_nLicenseState = licNotFound
10241171	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   207: m_nLicenseState = licNotFound (0x9DC8)
10241171	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   360: License state after ChlpLicenseManager::Read() is licNotFound
10241171	F:\CSLibs\MfcEx\hlpDongle.cpp   123: Failed to init loaded dongle
10241453	F:\CSLibs\MfcEx\devDongleWeb.cpp   176: Dongle ID matches!
10241453	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10241468	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10241468	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base)
10241468	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10396562	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   265: ChlpLicenseManager::Read()
10396562	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   162: ChlpLicenseManager::ReadDongle(int nAddr)
10397812	F:\CSLibs\MfcEx\devDongleWeb.cpp   587: Session ID: '93Qf6c8b41734k1e894edd27b5ege8ff' failed with error code 1
Description: ""10397812	F:\CSLibs\MfcEx\hlpDongle.cpp   235: Dumping buf Buffer
eJwzMFAwGEWjaBSNolFEX+Tmhg+ZmoFIAOmn8fA=
10397812	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   182: m_nLicenseState = licNotFound
10397812	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   207: m_nLicenseState = licNotFound (0x9DC8)
10397812	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   360: License state after ChlpLicenseManager::Read() is licNotFound
10397812	F:\CSLibs\MfcEx\hlpDongle.cpp   123: Failed to init loaded dongle
10398078	F:\CSLibs\MfcEx\devDongleWeb.cpp   176: Dongle ID matches!
10398078	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10398078	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10398078	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base)
10398078	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10406015	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10406015	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   764: ChlpLicenseManager::GetComponentLicenseLevel(CardioSmart Base)
10406015	F:\CSLibs\MfcEx\hlpLicenseManager.cpp   508: ChlpLicenseManager::IsLicenseValid()
10406937	F:\CSLibs\MfcEx\devDongleWeb.cpp   230: Freeing Dongle
10406937	F:\CSLibs\MfcEx\devDongleWeb.cpp   230: Freeing Dongle

Ну это, блин, просто праздник какой-то — шикарный подгон от разработчиков. Остается просто пропатчить метод ChlpLicenseManager::IsLicenseValid так, чтобы он всегда возвращал true.

Логи четко указали на цель: основная проверка лицензии завязана на метод IsLicenseValid. Посмотрим, как он выглядит в коде модуля MfcEx.dll:

Метод ChlpLicenseManager::IsLicenseValid
.text:106183F0 ; bool __thiscall ChlpLicenseManager::IsLicenseValid(ChlpLicenseManager *this)
.text:106183F0                 public ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ
.text:106183F0 ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ proc near
.text:106183F0                                         ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+2A↑p
.text:106183F0                                         ; DATA XREF: .rdata:off_1063C078↓o
.text:106183F0
.text:106183F0 pvtime          = qword ptr -40h
.text:106183F0 var_34          = byte ptr -34h
.text:106183F0 var_28          = byte ptr -28h
.text:106183F0 var_1C          = dword ptr -1Ch
.text:106183F0 var_18          = byte ptr -18h
.text:106183F0 var_10          = byte ptr -10h
.text:106183F0 var_C           = dword ptr -0Ch
.text:106183F0 var_4           = dword ptr -4
.text:106183F0 arg_4           = dword ptr  0Ch
.text:106183F0
.text:106183F0 ; FUNCTION CHUNK AT .text:10627EB2 SIZE 00000006 BYTES
.text:106183F0 ; FUNCTION CHUNK AT .text:1062A810 SIZE 0000002C BYTES
.text:106183F0 ; FUNCTION CHUNK AT .text:1062A841 SIZE 0000001D BYTES
.text:106183F0
.text:106183F0 ; __unwind { // ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ_SEH
.text:106183F0                 push    ebp
.text:106183F1                 mov     ebp, esp
.text:106183F3                 push    0FFFFFFFFh
.text:106183F5                 push    offset ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ_SEH
.text:106183FA                 mov     eax, large fs:0
.text:10618400                 push    eax
.text:10618401                 sub     esp, 34h
.text:10618404                 push    ebx
.text:10618405                 push    esi
.text:10618406                 push    edi
.text:10618407                 mov     eax, ___security_cookie
.text:1061840C                 xor     eax, ebp
.text:1061840E                 push    eax
.text:1061840F                 lea     eax, [ebp+var_C]
.text:10618412                 mov     large fs:0, eax
.text:10618418                 mov     edi, ecx
.text:1061841A                 cmp     dword ptr [edi+20h], 0
.text:1061841E                 mov     ebx, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10618424                 jz      short loc_1061847C
.text:10618426                 cmp     dword_1064B060, 0
.text:1061842D                 jz      short loc_1061847C
.text:1061842F                 cmp     dword_1064CE48, 2710h
.text:10618439                 jg      short loc_1061847C
.text:1061843B                 lea     ecx, [ebp+var_10]
.text:1061843E                 call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10618444                 push    1FCh
.text:10618449                 lea     eax, [ebp+var_10]
.text:1061844C ;   try {
.text:1061844C                 mov     [ebp+var_4], 0
.text:10618453                 push    offset aFCslibsMfcexHl_15 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10618458                 push    eax
.text:10618459                 call    ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:1061845B                 add     esp, 0Ch
.text:1061845E                 lea     eax, [ebp+var_10]
.text:10618461                 mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10618466                 push    eax
.text:10618467                 call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:1061846C                 lea     ecx, [ebp+var_10]
.text:1061846C ;   } // starts at 1061844C
.text:1061846F                 mov     [ebp+var_4], 0FFFFFFFFh
.text:10618476                 call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:1061847C
.text:1061847C loc_1061847C:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+34↑j
.text:1061847C                                         ; ChlpLicenseManager::IsLicenseValid(void)+3D↑j ...
.text:1061847C                 movzx   eax, word ptr [edi+20h]
.text:10618480                 lea     ecx, [ebp+var_28] ; this
.text:10618483                 push    1               ; int
.text:10618485                 push    edi             ; struct CSyncObject *
.text:10618486                 mov     [ebp+var_1C], eax
.text:10618489                 call    ??0CSingleLock@@QAE@PAVCSyncObject@@H@Z ; CSingleLock::CSingleLock(CSyncObject *,int)
.text:1061848E                 mov     esi, ds:GetTickCount
.text:10618494 ;   try {
.text:10618494                 mov     [ebp+var_4], 1
.text:1061849B                 call    esi ; GetTickCount
.text:1061849D                 sub     eax, [edi+2Ch]
.text:106184A0                 cmp     eax, 0EA60h
.text:106184A5                 jbe     loc_10618608
.text:106184AB                 call    esi ; GetTickCount
.text:106184AD                 mov     [edi+2Ch], eax
.text:106184B0                 cmp     dword_1064B060, 0
.text:106184B7                 jz      short loc_10618500
.text:106184B9                 cmp     dword_1064CE48, 2710h
.text:106184C3                 jg      short loc_10618500
.text:106184C5                 lea     ecx, [ebp+var_10]
.text:106184C8                 call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:106184CE                 push    20Ah
.text:106184D3                 lea     eax, [ebp+var_10]
.text:106184D3 ;   } // starts at 10618494
.text:106184D6 ;   try {
.text:106184D6                 mov     byte ptr [ebp+var_4], 2
.text:106184DA                 push    offset aFCslibsMfcexHl_16 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:106184DF                 push    eax
.text:106184E0                 call    ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:106184E2                 add     esp, 0Ch
.text:106184E5                 lea     eax, [ebp+var_10]
.text:106184E8                 mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:106184ED                 push    eax
.text:106184EE                 call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:106184F3                 lea     ecx, [ebp+var_10]
.text:106184F3 ;   } // starts at 106184D6
.text:106184F6 ;   try {
.text:106184F6                 mov     byte ptr [ebp+var_4], 1
.text:106184FA                 call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10618500
.text:10618500 loc_10618500:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+C7↑j
.text:10618500                                         ; ChlpLicenseManager::IsLicenseValid(void)+D3↑j
.text:10618500                 mov     ecx, [edi+20h]
.text:10618503                 test    ecx, ecx
.text:10618505                 jnz     loc_10618608
.text:1061850B                 mov     ecx, dword_1064B044
.text:10618511                 test    ecx, ecx
.text:10618513                 jz      short loc_10618523
.text:10618515                 mov     eax, [ecx]
.text:10618517                 mov     eax, [eax+18h]
.text:1061851A                 call    eax
.text:1061851C                 mov     ecx, [edi+20h]
.text:1061851F                 test    al, al
.text:10618521                 jnz     short loc_1061852F
.text:10618523
.text:10618523 loc_10618523:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+123↑j
.text:10618523                 mov     dword ptr [edi+20h], 3
.text:1061852A                 jmp     loc_10618608
.text:1061852F ; ---------------------------------------------------------------------------
.text:1061852F
.text:1061852F loc_1061852F:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+131↑j
.text:1061852F                 test    ecx, ecx
.text:10618531                 jnz     loc_10618608
.text:10618537                 mov     eax, [edi+1Ch]
.text:1061853A                 mov     ecx, 0FFFFh
.text:1061853F                 cmp     [eax+1D4h], cx
.text:10618546                 jz      loc_10618608
.text:1061854C                 mov     ecx, edi        ; this
.text:1061854E                 call    ?IsExecTimeValid@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::IsExecTimeValid(void)
.text:10618553                 test    al, al
.text:10618555                 jz      loc_10618601
.text:1061855B                 cmp     byte ptr [edi+7ACh], 0
.text:10618562                 jz      short loc_106185B3
.text:10618564                 cmp     byte ptr [edi+7AFh], 0
.text:1061856B                 jz      short loc_1061859F
.text:1061856D                 lea     eax, [edi+7A8h]
.text:10618573                 push    eax
.text:10618574                 lea     ecx, [ebp+var_10]
.text:10618577                 call    ds:??0?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAE@ABV01@@Z ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:1061857D                 mov     ecx, eax
.text:1061857D ;   } // starts at 106184F6
.text:1061857F ;   try {
.text:1061857F                 mov     byte ptr [ebp+var_4], 3
.text:10618583                 call    ds:?Trim@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAEAAV12@XZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(void)
.text:10618589                 lea     ecx, [ebp+var_10]
.text:10618589 ;   } // starts at 1061857F
.text:1061858C ;   try {
.text:1061858C                 mov     byte ptr [ebp+var_4], 1
.text:10618590                 mov     eax, [eax]
.text:10618592                 mov     esi, [eax-0Ch]
.text:10618595                 call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:1061859B                 test    esi, esi
.text:1061859D                 jz      short loc_106185B3
.text:1061859F
.text:1061859F loc_1061859F:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+17B↑j
.text:1061859F                 mov     ecx, edi        ; this
.text:106185A1                 call    ?CheckAndTryToExtendLicense@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::CheckAndTryToExtendLicense(void)
.text:106185A6                 test    al, al
.text:106185A8                 jnz     short loc_10618608
.text:106185AA                 mov     dword ptr [edi+20h], 3
.text:106185B1                 jmp     short loc_10618608
.text:106185B3 ; ---------------------------------------------------------------------------
.text:106185B3
.text:106185B3 loc_106185B3:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+172↑j
.text:106185B3                                         ; ChlpLicenseManager::IsLicenseValid(void)+1AD↑j
.text:106185B3                 cmp     dword ptr [edi+20h], 0
.text:106185B7                 jnz     short loc_10618608
.text:106185B9                 mov     ecx, [edi+1Ch]
.text:106185BC                 lea     eax, [ebp+var_18]
.text:106185BF                 push    eax
.text:106185C0                 call    ?GetLastExecTime@CtypLicenseManager@@QBE?AVCOleDateTime@ATL@@XZ ; CtypLicenseManager::GetLastExecTime(void)
.text:106185C5                 lea     eax, [ebp+var_18]
.text:106185C8                 push    eax
.text:106185C9                 lea     eax, [ebp+var_34]
.text:106185CC                 push    eax
.text:106185CD                 lea     eax, [ebp+pvtime]
.text:106185D0                 push    eax             ; pvtime
.text:106185D1                 call    sub_10618060
.text:106185D6                 mov     ecx, eax
.text:106185D8                 call    sub_10617660
.text:106185DD                 mov     ecx, eax
.text:106185DF                 call    sub_10618080
.text:106185E4                 fstp    qword ptr [ebp-14h]
.text:106185E7                 movsd   xmm0, qword ptr [ebp-14h]
.text:106185EC                 comisd  xmm0, ds:qword_106319D8
.text:106185F4                 jb      short loc_10618608
.text:106185F6                 push    1               ; bool
.text:106185F8                 mov     ecx, edi        ; this
.text:106185FA                 call    ?Save@ChlpLicenseManager@@QAEX_N@Z ; ChlpLicenseManager::Save(bool)
.text:106185FF                 jmp     short loc_10618608
.text:10618601 ; ---------------------------------------------------------------------------
.text:10618601
.text:10618601 loc_10618601:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+165↑j
.text:10618601                 mov     dword ptr [edi+20h], 2
.text:10618608
.text:10618608 loc_10618608:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+B5↑j
.text:10618608                                         ; ChlpLicenseManager::IsLicenseValid(void)+115↑j ...
.text:10618608                 mov     eax, [ebp+var_1C]
.text:1061860B                 movzx   eax, ax
.text:1061860E                 mov     byte ptr [edi+30h], 1
.text:10618612                 cmp     [edi+20h], eax
.text:10618615                 jz      short loc_1061866D
.text:10618617                 cmp     dword_1064B060, 0
.text:1061861E                 jz      short loc_1061866D
.text:10618620                 cmp     dword_1064CE48, 2710h
.text:1061862A                 jg      short loc_1061866D
.text:1061862C                 lea     ecx, [ebp+var_10]
.text:1061862F                 call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10618635                 mov     eax, [edi+20h]
.text:10618635 ;   } // starts at 1061858C
.text:10618638 ;   try {
.text:10618638                 mov     byte ptr [ebp+var_4], 4
.text:1061863C                 push    off_1064B2F0[eax*4] ; "licValid"
.text:10618643                 lea     eax, [ebp+var_10]
.text:10618646                 push    232h
.text:1061864B                 push    offset aFCslibsMfcexHl_17 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10618650                 push    eax
.text:10618651                 call    ebx ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10618653                 add     esp, 10h
.text:10618656                 lea     eax, [ebp+var_10]
.text:10618659                 mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:1061865E                 push    eax
.text:1061865F                 call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10618664                 lea     ecx, [ebp+var_10]
.text:10618667                 call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:1061866D
.text:1061866D loc_1061866D:                           ; CODE XREF: ChlpLicenseManager::IsLicenseValid(void)+225↑j
.text:1061866D                                         ; ChlpLicenseManager::IsLicenseValid(void)+22E↑j ...
.text:1061866D                 cmp     dword ptr [edi+20h], 0
.text:10618671                 setz    bl
.text:10618674                 lea     ecx, [ebp+var_28] ; this
.text:10618674 ;   } // starts at 10618638
.text:10618677 ;   try {
.text:10618677                 mov     [ebp+var_4], 5
.text:1061867E                 call    ?Unlock@CSingleLock@@QAEHXZ ; CSingleLock::Unlock(void)
.text:10618683                 mov     al, bl ; Меняем на mov     al, 1
.text:10618685                 mov     ecx, [ebp+var_C]
.text:10618688                 mov     large fs:0, ecx
.text:1061868F                 pop     ecx
.text:10618690                 pop     edi
.text:10618691                 pop     esi
.text:10618692                 pop     ebx
.text:10618693                 mov     esp, ebp
.text:10618695                 pop     ebp
.text:10618696                 retn
.text:10618696 ;   } // starts at 10618677
.text:10618696 ; } // starts at 106183F0
.text:10618696 ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ endp

Инструкцию mov  al, bl по адресу 10618683 заменил на mov   al, 1

Таким образом какая бы лицензия не была всегда метод ChlpLicenseManager::IsLicenseValid вернет true.

Но нельзя просто так взять и пропатчить одну функцию — это было бы слишком просто, а на практике так не бывает. Метод-то возвращает успех, но из-за отсутствия физического донгла программа падает. Отладка показала: краш происходит в методе ChlpLicenseManager::GetComponentLicenseLevel

Метод ChlpLicenseManager::GetComponentLicenseLevel
.text:10617AE0 ; __unwind { // ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z_SEH
.text:10617AE0    push    ebp
.text:10617AE1    mov     ebp, esp
.text:10617AE3    push    0FFFFFFFFh
.text:10617AE5    push    offset ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z_SEH
.text:10617AEA    mov     eax, large fs:0
.text:10617AF0    push    eax
.text:10617AF1    sub     esp, 20h
.text:10617AF4    push    ebx
.text:10617AF5    push    esi
.text:10617AF6    push    edi
.text:10617AF7    mov     eax, ___security_cookie
.text:10617AFC    xor     eax, ebp
.text:10617AFE    push    eax
.text:10617AFF    lea     eax, [ebp+var_C]
.text:10617B02    mov     large fs:0, eax
.text:10617B08    mov     esi, ecx
.text:10617B0A    call    ?IsLicenseValid@ChlpLicenseManager@@QAE_NXZ ; ChlpLicenseManager::IsLicenseValid(void)
.text:10617B0F    test    al, 1
.text:10617B11    jnz      short loc_10617B85 ; меняем на jz short loc_10617B85
.text:10617B13    cmp     dword_1064B060, 0
.text:10617B1A    jz      short loc_10617B6F
.text:10617B1C    cmp     dword_1064CE48, 2710h
.text:10617B26    jg      short loc_10617B6F
.text:10617B28    lea     ecx, [ebp+arg_4]
.text:10617B2B    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617B31    push    [ebp+ArgList]   ; ArgList
.text:10617B34 ;   try {
.text:10617B34    mov     [ebp+var_4], 0
.text:10617B3B    call    sub_10619780
.text:10617B40    push    eax
.text:10617B41    push    2FCh
.text:10617B46    lea     eax, [ebp+arg_4]
.text:10617B49    push    offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617B4E    push    eax
.text:10617B4F    call    ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617B55    add     esp, 14h
.text:10617B58    lea     eax, [ebp+arg_4]
.text:10617B5B    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617B60    push    eax
.text:10617B61    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617B66    lea     ecx, [ebp+arg_4]
.text:10617B69    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617B6F
.text:10617B6F loc_10617B6F:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+3A↑j
.text:10617B6F               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+46↑j
.text:10617B6F    xor     eax, eax
.text:10617B71    mov     ecx, [ebp+var_C]
.text:10617B74    mov     large fs:0, ecx
.text:10617B7B    pop     ecx
.text:10617B7C    pop     edi
.text:10617B7D    pop     esi
.text:10617B7E    pop     ebx
.text:10617B7F    mov     esp, ebp
.text:10617B81    pop     ebp
.text:10617B82    retn    8
.text:10617B85 ; ---------------------------------------------------------------------------
.text:10617B85
.text:10617B85 loc_10617B85:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+31↑j
.text:10617B85    push    1               ; int
.text:10617B87    push    esi             ; struct CSyncObject *
.text:10617B88    lea     ecx, [ebp+var_2C] ; this
.text:10617B8B    call    ??0CSingleLock@@QAE@PAVCSyncObject@@H@Z ; CSingleLock::CSingleLock(CSyncObject *,int)
.text:10617B90    mov     eax, [esi+1Ch]
.text:10617B93    lea     ecx, [ebp+var_D]
.text:10617B96    mov     ebx, [ebp+ArgList]
.text:10617B96 ;   } // starts at 10617B34
.text:10617B99 ;   try {
.text:10617B99    mov     [ebp+var_4], 1
.text:10617BA0    movzx   eax, byte ptr [eax+ebx] ; Падает вот здесь
.text:10617BA4    push    eax
.text:10617BA5    call    ??4CtypComponentLicense@@QAEAAV0@E@Z ; CtypComponentLicense::operator=(uchar) 
.text:10617BAA    mov     al, [ebp+var_D]
.text:10617BAD    mov     edi, [ebp+arg_4]
.text:10617BB0    movzx   ecx, al
.text:10617BB3    and     ecx, 0Fh
.text:10617BB6    cmp     ecx, edi
.text:10617BB8    jge     loc_10617C66
.text:10617BBE    mov     eax, dword_1064B060
.text:10617BC3    mov     esi, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617BC9    test    eax, eax
.text:10617BCB    jz      loc_10617D26
.text:10617BD1    cmp     dword_1064CE48, 2710h
.text:10617BDB    jg      short loc_10617C20
.text:10617BDD    lea     ecx, [ebp+ArgList]
.text:10617BE0    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617BE6    push    ebx             ; ArgList
.text:10617BE6 ;   } // starts at 10617B99
.text:10617BE7 ;   try {
.text:10617BE7    mov     byte ptr [ebp+var_4], 2
.text:10617BEB    call    sub_10619780
.text:10617BF0    push    eax
.text:10617BF1    push    309h
.text:10617BF6    lea     eax, [ebp+ArgList]
.text:10617BF9    push    offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617BFE    push    eax
.text:10617BFF    call    esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617C01    add     esp, 14h
.text:10617C04    lea     eax, [ebp+ArgList]
.text:10617C07    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617C0C    push    eax
.text:10617C0D    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617C12    lea     ecx, [ebp+ArgList]
.text:10617C15    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617C1B    mov     eax, dword_1064B060
.text:10617C20
.text:10617C20 loc_10617C20:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+FB↑j
.text:10617C20    test    eax, eax
.text:10617C22    jz      loc_10617D26
.text:10617C28    cmp     dword_1064CE48, 2710h
.text:10617C32    jg      loc_10617D26
.text:10617C38    lea     ecx, [ebp+ArgList]
.text:10617C3B    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617C41    movzx   eax, [ebp+var_D]
.text:10617C45    push    edi
.text:10617C46    and     eax, 0Fh
.text:10617C46 ;   } // starts at 10617BE7
.text:10617C49 ;   try {
.text:10617C49    mov     byte ptr [ebp+var_4], 3
.text:10617C4D    push    eax
.text:10617C4E    push    30Bh
.text:10617C53    lea     eax, [ebp+ArgList]
.text:10617C56    push    offset aFCslibsMfcexHl_12 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617C5B    push    eax
.text:10617C5C    call    esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617C5E    add     esp, 14h
.text:10617C61    jmp     loc_10617D0F
.text:10617C66 ; ---------------------------------------------------------------------------
.text:10617C66
.text:10617C66 loc_10617C66:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+D8↑j
.text:10617C66    test    al, 10h
.text:10617C68    jz      loc_10617D30
.text:10617C6E    mov     ecx, esi        ; this
.text:10617C70    call    ?HasLicenseExpired@ChlpLicenseManager@@QBE_NXZ ; ChlpLicenseManager::HasLicenseExpired(void)
.text:10617C75    test    al, al
.text:10617C77    jz      loc_10617D2D
.text:10617C7D    mov     eax, dword_1064B060
.text:10617C82    mov     esi, ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617C88    test    eax, eax
.text:10617C8A    jz      loc_10617D26
.text:10617C90    cmp     dword_1064CE48, 2710h
.text:10617C9A    jg      short loc_10617CDF
.text:10617C9C    lea     ecx, [ebp+ArgList]
.text:10617C9F    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617CA5    push    ebx             ; ArgList
.text:10617CA5 ;   } // starts at 10617C49
.text:10617CA6 ;   try {
.text:10617CA6    mov     byte ptr [ebp+var_4], 5
.text:10617CAA    call    sub_10619780
.text:10617CAF    push    eax
.text:10617CB0    push    310h
.text:10617CB5    lea     eax, [ebp+ArgList]
.text:10617CB8    push    offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617CBD    push    eax
.text:10617CBE    call    esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617CC0    add     esp, 14h
.text:10617CC3    lea     eax, [ebp+ArgList]
.text:10617CC6    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617CCB    push    eax
.text:10617CCC    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617CD1    lea     ecx, [ebp+ArgList]
.text:10617CD4    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617CDA    mov     eax, dword_1064B060
.text:10617CDF
.text:10617CDF loc_10617CDF:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+1BA↑j
.text:10617CDF    test    eax, eax
.text:10617CE1    jz      short loc_10617D26
.text:10617CE3    cmp     dword_1064CE48, 2710h
.text:10617CED    jg      short loc_10617D26
.text:10617CEF    lea     ecx, [ebp+ArgList]
.text:10617CF2    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617CF8    push    311h
.text:10617CFD    lea     eax, [ebp+ArgList]
.text:10617CFD ;   } // starts at 10617CA6
.text:10617D00 ;   try {
.text:10617D00    mov     byte ptr [ebp+var_4], 6
.text:10617D04    push    offset aFCslibsMfcexHl_13 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617D09    push    eax
.text:10617D0A    call    esi ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...) ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617D0C    add     esp, 0Ch
.text:10617D0F
.text:10617D0F loc_10617D0F:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+181↑j
.text:10617D0F    lea     eax, [ebp+ArgList]
.text:10617D12    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617D17    push    eax
.text:10617D18    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617D1D    lea     ecx, [ebp+ArgList]
.text:10617D20    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617D26
.text:10617D26 loc_10617D26:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+EB↑j
.text:10617D26               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+142↑j ...
.text:10617D26    xor     ebx, ebx
.text:10617D28    jmp     loc_10617E8E
.text:10617D2D ; ---------------------------------------------------------------------------
.text:10617D2D
.text:10617D2D loc_10617D2D:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+197↑j
.text:10617D2D    mov     al, [ebp+var_D]
.text:10617D30
.text:10617D30 loc_10617D30:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+188↑j
.text:10617D30    mov     edi, [ebp+ArgList]
.text:10617D33    movzx   ebx, al
.text:10617D36    shr     ebx, 5
.text:10617D39    inc     ebx
.text:10617D3A    lea     eax, [edi+0Dh]
.text:10617D3D    mov     [ebp+var_1C], ebx
.text:10617D40    lea     eax, [esi+eax*4]
.text:10617D43    mov     [ebp+var_20], eax
.text:10617D46    cmp     [eax], ebx
.text:10617D48    jz      loc_10617E8E
.text:10617D4E    mov     eax, dword_1064B060
.text:10617D53    test    eax, eax
.text:10617D55    jz      loc_10617E89
.text:10617D5B    cmp     dword_1064CE48, 2710h
.text:10617D65    jg      short loc_10617DAE
.text:10617D67    lea     ecx, [ebp+arg_4]
.text:10617D6A    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617D70    push    edi             ; ArgList
.text:10617D70 ;   } // starts at 10617D00
.text:10617D71 ;   try {
.text:10617D71    mov     byte ptr [ebp+var_4], 8
.text:10617D75    call    sub_10619780
.text:10617D7A    push    eax
.text:10617D7B    push    319h
.text:10617D80    lea     eax, [ebp+arg_4]
.text:10617D83    push    offset aFCslibsMfcexHl_11 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617D88    push    eax
.text:10617D89    call    ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617D8F    add     esp, 14h
.text:10617D92    lea     eax, [ebp+arg_4]
.text:10617D95    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617D9A    push    eax
.text:10617D9B    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617DA0    lea     ecx, [ebp+arg_4]
.text:10617DA3    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617DA9    mov     eax, dword_1064B060
.text:10617DAE
.text:10617DAE loc_10617DAE:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+285↑j
.text:10617DAE    test    eax, eax
.text:10617DB0    jz      loc_10617E89
.text:10617DB6    cmp     dword_1064CE48, 2710h
.text:10617DC0    jg      loc_10617E89
.text:10617DC6    lea     ecx, [ebp+var_18]
.text:10617DC9    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:10617DCF    movzx   edi, [ebp+var_D]
.text:10617DD3    lea     ecx, [ebp+arg_4+3] ; this
.text:10617DD6    xor     esi, esi
.text:10617DD6 ;   } // starts at 10617D71
.text:10617DD8 ;   try {
.text:10617DD8    mov     byte ptr [ebp+var_4], 9
.text:10617DDC    shr     edi, 5
.text:10617DDF    mov     [ebp+var_14], esi
.text:10617DE2    call    ?GetSize@CarrComponents@@QBEHXZ ; CarrComponents::GetSize(void)
.text:10617DE7    test    eax, eax
.text:10617DE9    jle     short loc_10617E41
.text:10617DEB    mov     ebx, offset ?m_arrComponents@CarrComponents@@1QBUCtypDongleComponents@@B ; CtypDongleComponents const * const CarrComponents::m_arrComponents
.text:10617DF0
.text:10617DF0 loc_10617DF0:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+35C↓j
.text:10617DF0    mov     eax, [ebp+ArgList]
.text:10617DF3    cmp     [ebx], eax
.text:10617DF5    jnz     short loc_10617E2B
.text:10617DF7    mov     ecx, [ebx+10h]
.text:10617DFA    xor     eax, eax
.text:10617DFC    mov     esi, offset Buffer
.text:10617E01    cmp     [ecx], ax
.text:10617E04    jz      short loc_10617E28
.text:10617E06
.text:10617E06 loc_10617E06:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+346↓j
.text:10617E06    cmp     eax, edi
.text:10617E08    jg      short loc_10617E28
.text:10617E0A    movzx   edx, word ptr [ecx]
.text:10617E0D    cmp     edx, 0Ah
.text:10617E10    jnz     short loc_10617E15
.text:10617E12    inc     eax
.text:10617E13    jmp     short loc_10617E1F
.text:10617E15 ; ---------------------------------------------------------------------------
.text:10617E15
.text:10617E15 loc_10617E15:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+330↑j
.text:10617E15    cmp     eax, edi
.text:10617E17    jnz     short loc_10617E1F
.text:10617E19    mov     [esi], dx
.text:10617E1C    add     esi, 2
.text:10617E1F
.text:10617E1F loc_10617E1F:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+333↑j
.text:10617E1F               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+337↑j
.text:10617E1F    add     ecx, 2
.text:10617E22    cmp     word ptr [ecx], 0
.text:10617E26    jnz     short loc_10617E06
.text:10617E28
.text:10617E28 loc_10617E28:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+324↑j
.text:10617E28               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+328↑j
.text:10617E28    mov     esi, [ebp+var_14]
.text:10617E2B
.text:10617E2B loc_10617E2B:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+315↑j
.text:10617E2B    inc     esi
.text:10617E2C    lea     ecx, [ebp+arg_4+3] ; this
.text:10617E2F    mov     [ebp+var_14], esi
.text:10617E32    add     ebx, 14h
.text:10617E35    call    ?GetSize@CarrComponents@@QBEHXZ ; CarrComponents::GetSize(void)
.text:10617E3A    cmp     esi, eax
.text:10617E3C    jl      short loc_10617DF0
.text:10617E3E    mov     ebx, [ebp+var_1C]
.text:10617E41
.text:10617E41 loc_10617E41:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+309↑j
.text:10617E41    lea     eax, [edi+1]
.text:10617E44    push    eax             ; ArgList
.text:10617E45    push    offset aD       ; "%d"
.text:10617E4A    push    18h             ; BufferCount
.text:10617E4C    push    offset Buffer   ; Buffer
.text:10617E51    call    sub_106197F0
.text:10617E56    push    offset Buffer
.text:10617E5B    push    31Ah
.text:10617E60    lea     eax, [ebp+var_18]
.text:10617E63    push    offset aFCslibsMfcexHl_14 ; "F:\\CSLibs\\MfcEx\\hlpLicenseManager.cp"...
.text:10617E68    push    eax
.text:10617E69    call    ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:10617E6F    add     esp, 20h
.text:10617E72    lea     eax, [ebp+var_18]
.text:10617E75    mov     ecx, offset ?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:10617E7A    push    eax
.text:10617E7B    call    ?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:10617E80    lea     ecx, [ebp+var_18]
.text:10617E83    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:10617E89
.text:10617E89 loc_10617E89:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+275↑j
.text:10617E89               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+2D0↑j ...
.text:10617E89    mov     eax, [ebp+var_20]
.text:10617E8C    mov     [eax], ebx
.text:10617E8E
.text:10617E8E loc_10617E8E:              ; CODE XREF: ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+248↑j
.text:10617E8E               ; ChlpLicenseManager::GetComponentLicenseLevel(enumComponentLM,int)+268↑j
.text:10617E8E    lea     ecx, [ebp+var_2C] ; this
.text:10617E8E ;   } // starts at 10617DD8
.text:10617E91 ;   try {
.text:10617E91    mov     [ebp+var_4], 0Ah
.text:10617E98    call    ?Unlock@CSingleLock@@QAEHXZ ; CSingleLock::Unlock(void)
.text:10617E9D    mov     eax, ebx
.text:10617E9F    mov     ecx, [ebp+var_C]
.text:10617EA2    mov     large fs:0, ecx
.text:10617EA9    pop     ecx
.text:10617EAA    pop     edi
.text:10617EAB    pop     esi
.text:10617EAC    pop     ebx
.text:10617EAD    mov     esp, ebp
.text:10617EAF    pop     ebp
.text:10617EB0    retn    8
.text:10617EB0 ;   } // starts at 10617E91
.text:10617EB0 ; } // starts at 10617AE0
.text:10617EB0 ?GetComponentLicenseLevel@ChlpLicenseManager@@QAEHW4enumComponentLM@@H@Z endp

Патч получился такой:

Инструкцию jnz short loc_10617B85 по адресу 10617B11 заменил на jz short loc_10617B85.

Инверсия условия позволила проскочить участок с неинициализированными данными, и программа перестала падать. В диалоге активации я ввел произвольный серийный номер. После нажатия "Activate" приложение попросило перезапуск.

После рестарта диалог исчез, и открылось главное окно. Я откатил патч, и программа продолжила запускаться штатно.

Импорт .xcm файла и снова проверка лицензии

Казалось бы победа!  Но рано радоваться: при попытке импортировать файл с результатами холтера вылезло новое сообщение:

Понятно где-то есть еще одна проверка лицензии. Я глянул лог CardioMg.0.log и увидел там:

2026-02-11 16:45:43 <MAIN:WARN > {MultiCom.dll::CappMain::ImportExam @1763}: Exception occurred [CxcpCardioMg]: This operation is not supported in this Demo version or license is over.
                    Contact our Sales Department for more information on this product @ (55)(11)3883-3010.
                    Exception intercepted! Rethrowing.

Отлично! Исключение обрабатывается в методе CappMain::ImportExam из модуля MultiCom.dll. По стеку видно что исключение было брошено из метода CdtMultiDocTemplate::ThrowIfNotExample:

Address 	        Module  	Function
0000000076E40D72	KERNELBASE.dll	kernelbase_RaiseException+62
00000000753B4971	VCRUNTIME140.dll	vcruntime140__CxxThrowException+61
000000001111B905	MultiCom.dll	public: virtual void __thiscall CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+0xE5
00000000111058A4	MultiCom.dll	public: void __thiscall CappMain::ImportExam(class CStringArray const &)+0x564
Методы CdtMultiDocTemplate::ThrowIfNotExample и CdtCardioMg::AssertForExample

Метод CdtMultiDocTemplate::ThrowIfNotExample:

; void __thiscall CdtMultiDocTemplate::ThrowIfNotExample(CdtMultiDocTemplate *this, wchar_t *)
.text:1111B820    public ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z
.text:1111B820 ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z proc near
.text:1111B820               ; DATA XREF: .rdata:1113B698↓o
.text:1111B820               ; .rdata:off_11146E98↓o
.text:1111B820
.text:1111B820 pExceptionObject= dword ptr -10h
.text:1111B820 var_C           = dword ptr -0Ch
.text:1111B820 var_4           = dword ptr -4
.text:1111B820 arg_0           = dword ptr  8
.text:1111B820 arg_4           = dword ptr  0Ch
.text:1111B820
.text:1111B820 ; FUNCTION CHUNK AT .text:11132890 SIZE 00000013 BYTES
.text:1111B820 ; FUNCTION CHUNK AT .text:111328A8 SIZE 0000001D BYTES
.text:1111B820
.text:1111B820 ; __unwind { // ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z_SEH
.text:1111B820    push    ebp
.text:1111B821    mov     ebp, esp
.text:1111B823    push    0FFFFFFFFh
.text:1111B825    push    offset ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z_SEH
.text:1111B82A    mov     eax, large fs:0
.text:1111B830    push    eax
.text:1111B831    push    ecx
.text:1111B832    push    esi
.text:1111B833    mov     eax, ___security_cookie
.text:1111B838    xor     eax, ebp
.text:1111B83A    push    eax
.text:1111B83B    lea     eax, [ebp+var_C]
.text:1111B83E    mov     large fs:0, eax
.text:1111B844    mov     esi, ecx
.text:1111B846    mov     eax, [esi]
.text:1111B848    push    [ebp+arg_0]
.text:1111B84B    mov     eax, [eax+0A4h]
.text:1111B851    call    eax ; Вызов CdtCardioMg::AssertForExample из модуля ModulesBase.dll
.text:1111B853    test    al, al
.text:1111B855    jnz     short loc_1111B8B5
.text:1111B857    cmp     dword ptr [esi+0B0h], 1
.text:1111B85E    jnz     short loc_1111B8C7
.text:1111B860    call    sub_1111B920
.text:1111B865    mov     ecx, ds:?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:1111B86B    call    ds:?CanLog@ChlpDongle@@QAE_NXZ ; ChlpDongle::CanLog(void)
.text:1111B871    test    al, al
.text:1111B873    jz      short loc_1111B8B5
.text:1111B875    lea     ecx, [ebp+arg_0]
.text:1111B878    call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(void)
.text:1111B87E    push    0A2h
.text:1111B883    lea     eax, [ebp+arg_0]
.text:1111B886 ;   try {
.text:1111B886    mov     [ebp+var_4], 1
.text:1111B88D    push    offset aFCardiomgModul ; "F:\\CardioMg\\ModulesBase\\dtCardioMg.h"...
.text:1111B892    push    eax
.text:1111B893    call    ds:?Format@?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAAXPB_WZZ ; ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(wchar_t const *,...)
.text:1111B899    mov     ecx, ds:?g_DongleDevice@@3VChlpDongle@@A ; ChlpDongle g_DongleDevice
.text:1111B89F    lea     eax, [ebp+arg_0]
.text:1111B8A2    add     esp, 0Ch
.text:1111B8A5    push    eax
.text:1111B8A6    call    ds:?LogString@ChlpDongle@@QAEXABV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; ChlpDongle::LogString(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const &)
.text:1111B8AC    lea     ecx, [ebp+arg_0]
.text:1111B8AF    call    ds:??1XID@CMFCRibbonInfo@@QAE@XZ ; CMFCRibbonInfo::XID::~XID(void)
.text:1111B8B5
.text:1111B8B5 loc_1111B8B5:              ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+35↑j
.text:1111B8B5               ; CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+53↑j
.text:1111B8B5    mov     ecx, [ebp+var_C]
.text:1111B8B8    mov     large fs:0, ecx
.text:1111B8BF    pop     ecx
.text:1111B8C0    pop     esi
.text:1111B8C1    mov     esp, ebp
.text:1111B8C3    pop     ebp
.text:1111B8C4    retn    4
.text:1111B8C7 ; ---------------------------------------------------------------------------
.text:1111B8C7
.text:1111B8C7 loc_1111B8C7:              ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+3E↑j
.text:1111B8C7    push    10h
.text:1111B8C9    call    sub_11102090
.text:1111B8CE    mov     esi, eax
.text:1111B8D0    mov     [ebp+arg_0], esi
.text:1111B8D0 ;   } // starts at 1111B886
.text:1111B8D3 ;   try {
.text:1111B8D3    mov     [ebp+var_4], 0
.text:1111B8DA    test    esi, esi
.text:1111B8DC    jz      short loc_1111B8F0
.text:1111B8DE    push    0Eh
.text:1111B8E0    mov     ecx, esi
.text:1111B8E2    call    ds:??0CxcpCardioMg@@QAE@H@Z ; CxcpCardioMg::CxcpCardioMg(int)
.text:1111B8E8    mov     dword ptr [esi], offset ??_7CxcpCardioMg@@6B@ ; const CxcpCardioMg::`vftable'
.text:1111B8EE    jmp     short loc_1111B8F2
.text:1111B8F0 ; ---------------------------------------------------------------------------
.text:1111B8F0
.text:1111B8F0 loc_1111B8F0:              ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+BC↑j
.text:1111B8F0    xor     esi, esi
.text:1111B8F2
.text:1111B8F2 loc_1111B8F2:              ; CODE XREF: CdtMultiDocTemplate::ThrowIfNotExample(wchar_t const *)+CE↑j
.text:1111B8F2    push    offset __TI4PAVCxcpCardioMg@@ ; pThrowInfo
.text:1111B8F7    lea     eax, [ebp+pExceptionObject]
.text:1111B8F7 ;   } // starts at 1111B8D3
.text:1111B8FA    mov     [ebp+var_4], 0FFFFFFFFh
.text:1111B901    push    eax             ; pExceptionObject
.text:1111B902    mov     [ebp+pExceptionObject], esi
.text:1111B905    call    _CxxThrowException
.text:1111B905 ; } // starts at 1111B820
.text:1111B905 ?ThrowIfNotExample@CdtMultiDocTemplate@@UAEXPB_W@Z endp

Метод CdtCardioMg::AssertForExample:

.text:00CC8A40 ; bool __thiscall CdtCardioMg::AssertForExample(CdtCardioMg *__hidden this, const wchar_t *)
.text:00CC8A40                 public ?AssertForExample@CdtCardioMg@@UAE_NPB_W@Z
.text:00CC8A40 ?AssertForExample@CdtCardioMg@@UAE_NPB_W@Z proc near
.text:00CC8A40                                         ; DATA XREF: .rdata:00CD0BD4↓o
.text:00CC8A40                                         ; .rdata:off_CD4228↓o
.text:00CC8A40
.text:00CC8A40 arg_0           = dword ptr  8
.text:00CC8A40
.text:00CC8A40                 push    ebp
.text:00CC8A41                 mov     ebp, esp
.text:00CC8A43                 mov     eax, [ebp+arg_0]
.text:00CC8A46                 cmp     word ptr [eax], 58h ; 'X'
.text:00CC8A4A                 jnz     short loc_CC8A66
.text:00CC8A4C                 add     eax, 6
.text:00CC8A4F                 push    eax             ; String
.text:00CC8A50                 call    ds:_wtoi
.text:00CC8A56                 add     esp, 4
.text:00CC8A59                 cmp     eax, 26DEh
.text:00CC8A5E                 jl      short loc_CC8A66
.text:00CC8A60                 mov     al, 1
.text:00CC8A62                 pop     ebp
.text:00CC8A63                 retn    4
.text:00CC8A66 ; ---------------------------------------------------------------------------
.text:00CC8A66
.text:00CC8A66 loc_CC8A66:                             ; CODE XREF: CdtCardioMg::AssertForExample(wchar_t const *)+A↑j
.text:00CC8A66                                         ; CdtCardioMg::AssertForExample(wchar_t const *)+1E↑j
.text:00CC8A66                 xor     al, al ; Меняем на mov     al, 1
.text:00CC8A68                 pop     ebp
.text:00CC8A69                 retn    4

Метод CdtMultiDocTemplate::ThrowIfNotExample вызывает метод CdtCardioMg::AssertForExampleи при результате равным true бросает исключение. Я пропатчил метод CdtCardioMg::AssertForExample так: заменил инструкцию xor  al, al по адресу 00CC8A66на mov al, 1

С помощью этого патча импорт наконец-то завершился успешно!

Открытие импортированного документа или снова хождение по мукам

Но опять рано радоваться: при попытке открыть импортированный документ появилось еще одно сообщение:

Я снова заглянул в лог CardioMg.0.log и увидел там:

2026-02-11 17:53:33 <MAIN:WARN > {MultiCom.dll::CfrmMain::OnFileOpen @352}: Exception occurred [CxcpDongle]: Operation not permitted. Check the license validity or a fail with the dongle hardware.
                    Trying to recover license.

Ага, значит исключение обрабатывается в методе CfrmMain::OnFileOpen модуля MultiCom.dll. Гдянул стек вызовов:

Address 	Module 	Function
0000000076E40D72	KERNELBASE.dll	kernelbase_RaiseException+62
00000000753B4971	VCRUNTIME140.dll	vcruntime140__CxxThrowException+61
000000001241CC45	Holter.cmm	holter_?OnOpenDocument@CdocHolter@@UAEHPB_W@Z+5E5
000000005964D545	mfc140u.dll	mfc140u_11979+125
000000005964D411	mfc140u.dll	mfc140u_11978+21
000000001111B5CF	MultiCom.dll	public: virtual class CDocument * __thiscall CdtMultiDocTemplate::OpenDocumentFile(wchar_t const *,int)+0xCF
0000000011107B69	MultiCom.dll	public: class CDocument * __thiscall CappMain::OpenDocTemplate(unsigned int,wchar_t const *)+0x39
000000001111E856	MultiCom.dll	protected: void __thiscall CfrmMain::OnFileOpen(void)+0x106

Судя по стеку исключение выбрасывается в методе CdocHolter::OnOpenDocument из модуля Holter.cmm

Метод CdocHolter::OnOpenDocument
int __thiscall CdocHolter::OnOpenDocument(CdocHolter *this, const wchar_t *a2)
{
  int App; // eax
  int TotalOpenedMasterDocs; // esi
  struct AFX_MODULE_STATE *ModuleState; // eax
  int v6; // edx
  ChlpDongle *v7; // ecx
  int v8; // edx
  int v9; // eax
  int v10; // ecx
  ChlpLicenseManager *v11; // esi
  int DeviceName; // eax
  char v13; // bl
  int v14; // eax
  int Schema; // eax
  HCURSOR CursorW; // eax
  int v17; // eax
  CdtHolter *v18; // esi
  const wchar_t *v19; // eax
  struct CWinThread *Thread; // ecx
  int v21; // eax
  CdbCamX *v22; // esi
  int v23; // ebx
  int PreviousMdbVersion; // eax
  unsigned __int8 v25; // bl
  char v26; // al
  int v27; // ecx
  const wchar_t *v29; // eax
  int v30; // eax
  const wchar_t *v31; // eax
  wchar_t *v32; // eax
  wchar_t *v33; // esi
  wchar_t *v34; // eax
  wchar_t *v35; // esi
  int v36; // [esp+0h] [ebp-108h] BYREF
  int pExceptionObject[2]; // [esp+D8h] [ebp-30h] BYREF
  char v38[4]; // [esp+E0h] [ebp-28h] BYREF
  wchar_t *v39; // [esp+E4h] [ebp-24h] BYREF
  int v40; // [esp+E8h] [ebp-20h]
  char v41[4]; // [esp+F0h] [ebp-18h] BYREF
  int v42[2]; // [esp+F4h] [ebp-14h] BYREF
  int v43; // [esp+104h] [ebp-4h]

  v42[1] = (int)&v36;
  pExceptionObject[1] = (int)this;
  v40 = 0;
  App = CappMain::GetApp();
  if ( ChlpLicenseManager::GetComponentLicenseLevel(App + 332, 8, 1) <= 0 )
  {
    if ( ChlpDongle::CanLog(g_DongleDevice) )
    {
      ((void (__thiscall *)(const wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&a2);
      v43 = 0;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(
        &a2,
        L"F:\\CardioSmart\\Holter\\docHolter.cpp%6d: Platform level is unlicensed/expired.\n",
        1310);
      ChlpDongle::LogString(g_DongleDevice, &a2);
      v43 = -1;
      ((void (__thiscall *)(const wchar_t **))CMFCRibbonInfo::XID::~XID)(&a2);
    }
    v34 = (wchar_t *)sub_1240D1B0(0xCu);
    v35 = v34;
    a2 = v34;
    v43 = 1;
    if ( v34 )
    {
      CxcpDongle::CxcpDongle((CxcpDongle *)v34, 0);
      *(_DWORD *)v35 = &CxcpDongle::`vftable';
    }
    else
    {
      v35 = 0;
    }
    v43 = -1;
    v39 = v35;
    CxxThrowException(&v39, (_ThrowInfo *)&_TI4PAVCxcpDongle__);
  }
  TotalOpenedMasterDocs = CdocBase::GetTotalOpenedMasterDocs(
                            this,
                            (const struct CRuntimeClass *)&CdocHolter::classCdocHolter);
  ModuleState = AfxGetModuleState();
  if ( TotalOpenedMasterDocs > (*(int (__thiscall **)(_DWORD, const wchar_t *, const wchar_t *, int))(**((_DWORD **)ModuleState + 1) + 132))(
                                 *((_DWORD *)ModuleState + 1),
                                 L"Holter",
                                 L"MaxOpenExams",
                                 7)
    && AfxMessageBox(0x844u, 0x104u, 0xFFFFFFFF) != 6
    || !CdocBase::OnOpenDocument(this, a2) )
  {
    return 0;
  }
  CUIntArray::SetAtGrow(
    (CdocHolter *)((char *)this + 2560),
    *((_DWORD *)this + 642),
    (CdocHolter *)((char *)this + 2584) != 0 ? (unsigned int)this + 5304 : 0);
  v6 = *((_DWORD *)this + 270);
  if ( v6 )
    CUIntArray::SetAtGrow(
      (CdocHolter *)((char *)this + 2560),
      *((_DWORD *)this + 642),
      *(_DWORD *)(v6 + 844) != 0 ? *(_DWORD *)(v6 + 844) + 360 : 0);
  v7 = (ChlpDongle *)g_DongleDevice;
  v8 = *((_DWORD *)&g_DongleDevice + 1);
  if ( v8 )
  {
    (*(void (__thiscall **)(_DWORD))(*(_DWORD *)v8 + 28))(*((_DWORD *)&g_DongleDevice + 1));
    v7 = (ChlpDongle *)g_DongleDevice;
  }
  if ( ChlpDongle::CanLog(v7) )
  {
    ((void (__thiscall *)(const wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&a2);
    v43 = 2;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(
      &a2,
      L"F:\\CardioSmart\\Holter\\docHolter.h%6d: Dongle is present: let's test validity.\n",
      755);
    ChlpDongle::LogString(g_DongleDevice, &a2);
    v43 = -1;
    ((void (__thiscall *)(const wchar_t **))CMFCRibbonInfo::XID::~XID)(&a2);
  }
  v9 = CappMain::GetApp();
  v10 = *(_DWORD *)(v9 + 360);
  if ( !v10 || !*(_DWORD *)(v10 + 474) )
    goto LABEL_47;
  v11 = (ChlpLicenseManager *)(v9 + 332);
  DeviceName = ChlpLicenseManager::GetDeviceName(v9 + 332, v41);
  v13 = 1;
  v43 = 3;
  v40 = 1;
  if ( ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CompareNoCase(
         DeviceName,
         L"Compact-500")
    && (v14 = ChlpLicenseManager::GetDeviceName(v11, v38),
        v13 = 3,
        v43 = 4,
        v40 = 3,
        ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CompareNoCase(v14, L"NeoKey"))
    || (Schema = ChlpDongle::GetSchema(g_DongleDevice), HIBYTE(a2) = 1, Schema >= 1) )
  {
    HIBYTE(a2) = 0;
  }
  if ( (v13 & 2) != 0 )
  {
    v13 &= ~2u;
    ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v38);
  }
  v43 = -1;
  if ( (v13 & 1) != 0 )
    ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v41);
  if ( HIBYTE(a2) || !ChlpLicenseManager::IsLicenseValid(v11) )
  {
LABEL_47:
    v32 = (wchar_t *)sub_1240D1B0(0xCu);
    v33 = v32;
    a2 = v32;
    v43 = 5;
    if ( v32 )
    {
      CxcpDongle::CxcpDongle((CxcpDongle *)v32, 0);
      *(_DWORD *)v33 = &CxcpDongle::`vftable';
    }
    else
    {
      v33 = 0;
    }
    v43 = -1;
    pExceptionObject[0] = (int)v33;
    CxxThrowException(pExceptionObject, (_ThrowInfo *)&_TI4PAVCxcpDongle__);
  }
  CursorW = LoadCursorW(0, (LPCWSTR)0x7F02);
  v42[0] = (int)SetCursor(CursorW);
  v17 = *(_DWORD *)this;
  v18 = (CdtHolter *)*((_DWORD *)this + 10);
  v43 = 7;
  v19 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(v17 + 304))(this);
  CdtHolter::AddExamInstance(v18, v19, this);
  CdocHolter::ConfigureWorkDBFolder(this);
  CdocHolter::InitExamParams(this, v41);
  if ( *((_BYTE *)this + 1141) )
  {
    Thread = AfxGetThread();
    if ( Thread )
      v21 = (*(int (__thiscall **)(struct CWinThread *))(*(_DWORD *)Thread + 124))(Thread);
    else
      v21 = 0;
    v22 = (CdocHolter *)((char *)this + 1168);
    v23 = *(_DWORD *)CdbCamX::CheckOpen((char *)this + 1168, &a2, v21);
  }
  else
  {
    v23 = 4;
    v22 = (CdocHolter *)((char *)this + 1168);
  }
  a2 = (const wchar_t *)v23;
  if ( (v23 & 0x20) != 0 )
  {
    PreviousMdbVersion = CdbCamX::GetPreviousMdbVersion(v22);
    CdtHolter::AdjustPluginsSettings(
      *((CdtHolter **)this + 10),
      (CdocHolter *)((char *)this + 5312),
      PreviousMdbVersion,
      19);
    v25 = v23 & 0xDF;
  }
  else
  {
    v25 = (unsigned __int8)a2;
  }
  v26 = v25 | v41[0];
  if ( ((v25 | v41[0]) & 1) != 0 )
  {
LABEL_39:
    ChlpWaitCursor::~ChlpWaitCursor((ChlpWaitCursor *)v42);
    return 0;
  }
  if ( *((_BYTE *)this + 1141) && (v26 & 4) != 0 )
  {
    LOBYTE(v43) = 8;
    CdocHolter::CallCAMEx(this, 0);
    v27 = *((_DWORD *)this + 538);
    if ( (!v27 || ChlpPath::Exists((ChlpPath *)(v27 + 88)))
      && (*(_BYTE *)CdbCamX::CheckCamMdbVersion(v22, &a2, 0, 0) & 0x1F) != 0
      && AfxMessageBox(0x79Fu, 0x31u, 0xFFFFFFFF) != 1 )
    {
      goto LABEL_39;
    }
    v43 = 7;
  }
  else if ( (v26 & 8) != 0 )
  {
    v29 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(*(_DWORD *)this + 304))(this);
    CdtHolter::DeleteFilesFromExam(v29, 1);
    *((_BYTE *)this + 1133) = 1;
    v30 = sub_124142C0();
    PostMessageW(*(HWND *)(v30 + 32), 0x111u, 0x8833u, 0);
  }
  else if ( (v26 & 0x10) != 0 )
  {
    v31 = (const wchar_t *)(*(int (__thiscall **)(CdocHolter *))(*(_DWORD *)this + 304))(this);
    CdtHolter::DeleteFilesFromExam(v31, 1);
  }
  CdocHolter::Init24hsGraphics(this);
  CdocHolter::AttachFilesToDoc(this);
  ChlpWaitCursor::~ChlpWaitCursor((ChlpWaitCursor *)v42);
  return 1;
}

Я пропатчил метод ChlpLicenseManager::GetComponentLicenseLevel чтобы он возращал 1 если второй аргумент равен 8

Почему именно так? Потому что, если тупо возвращать 1 для всех вызовов этого метода, программа решит, что активирована лицензия для ветеринаров. Интерфейс перестроится, и все кнопки для работы с человеческой ЭКГ станут недоступны.

Патч в итоге получился такой:

.text:10617BAD     mov     edi, 8
.text:10617BB2     jmp     short loc_10617BB6 ; Прыгаем через мусор
.text:10617BB2 ; ---------------------------------------------------------------------------
.text:10617BB4     db 0E1h
.text:10617BB5     db  0Fh
.text:10617BB6 ; ---------------------------------------------------------------------------
.text:10617BB6
.text:10617BB6 loc_10617BB6:                           
.text:10617BB6     cmp     ebx, edi
.text:10617BB8     jz      loc_10617C66

И наконец все заработало без аппаратного ключа и лицензии!

Таким макаром мне удалось отучить программу от ключа и теперь я могу преступить непосредственно реверс-инжинирингу и уже анализировать код в динамике.

Часть 2: Реверс-инжиниринг

При импорте выбранного файла вызывается уже упомянутый мною в прошлой части статьи метод CappMain::ImportExam из модуля MultiCom.dll.

Метод CappMain::ImportExam
void __thiscall CappMain::ImportExam(CappMain *this, const struct CStringArray *a2)
{
  int ComponentLicenseLevel; // esi
  int v3; // ebx
  int v4; // eax
  int v5; // eax
  int v6; // ebx
  int v7; // eax
  int v8; // esi
  int DocTemplateByAnalysisMode; // ecx
  CengSignalFilesMinimal *v10; // eax
  char v11; // al
  wchar_t **v12; // ecx
  int v13; // eax
  wchar_t **v14; // ecx
  int v15; // ecx
  CengSignalFilesMinimal *v16; // eax
  int v17; // eax
  struct CdtCardioMg *v18; // esi
  CengSignalFilesMinimal *v19; // eax
  CMapStringToPtr *v20; // ecx
  unsigned int v21; // ebx
  int i; // esi
  __int16 v23; // si
  int v24; // esi
  int v25; // edi
  int v26; // eax
  unsigned int j; // edx
  __int16 v28; // bx
  __int16 ExamCount; // di
  __int16 v30; // di
  int StringRepresentation; // [esp-10h] [ebp-808h]
  int v32; // [esp+0h] [ebp-7F8h]
  int Extension; // [esp+8h] [ebp-7F0h]
  int v34; // [esp+8h] [ebp-7F0h]
  int v35; // [esp+8h] [ebp-7F0h]
  int v36; // [esp+20h] [ebp-7D8h] BYREF
  char v37[520]; // [esp+30h] [ebp-7C8h] BYREF
  char v38[64]; // [esp+238h] [ebp-5C0h] BYREF
  int v39[50]; // [esp+278h] [ebp-580h] BYREF
  int v40; // [esp+340h] [ebp-4B8h]
  __int16 v41[56]; // [esp+3BCh] [ebp-43Ch] BYREF
  char v42[20]; // [esp+430h] [ebp-3C8h] BYREF
  int v43[2]; // [esp+444h] [ebp-3B4h] BYREF
  int v44[2]; // [esp+44Ch] [ebp-3ACh] BYREF
  int v45[2]; // [esp+46Ch] [ebp-38Ch] BYREF
  void **v46; // [esp+474h] [ebp-384h] BYREF
  int v47; // [esp+478h] [ebp-380h]
  unsigned int v48; // [esp+47Ch] [ebp-37Ch]
  __int128 v49; // [esp+480h] [ebp-378h]
  char v50[4]; // [esp+490h] [ebp-368h] BYREF
  char v51[4]; // [esp+494h] [ebp-364h] BYREF
  int *v52; // [esp+498h] [ebp-360h]
  CappMain *v53; // [esp+4A0h] [ebp-358h]
  char v54[4]; // [esp+4A4h] [ebp-354h] BYREF
  wchar_t *v55; // [esp+4A8h] [ebp-350h]
  char v56[4]; // [esp+4ACh] [ebp-34Ch] BYREF
  char v57[4]; // [esp+4B0h] [ebp-348h] BYREF
  int v58; // [esp+4BCh] [ebp-33Ch]
  const struct CStringArray *v59; // [esp+4C0h] [ebp-338h]
  char v60; // [esp+4C4h] [ebp-334h] BYREF
  wchar_t *v61; // [esp+4C8h] [ebp-330h] BYREF
  wchar_t *v62; // [esp+4CCh] [ebp-32Ch] BYREF
  int v63; // [esp+4D0h] [ebp-328h]
  wchar_t *v64; // [esp+4D4h] [ebp-324h] BYREF
  CappMain *v65; // [esp+4D8h] [ebp-320h]
  int v66; // [esp+4DCh] [ebp-31Ch]
  int v67; // [esp+4E0h] [ebp-318h]
  wchar_t *v68; // [esp+4E4h] [ebp-314h] BYREF
  wchar_t *v69[2]; // [esp+4E8h] [ebp-310h] BYREF
  wchar_t *v70; // [esp+4F0h] [ebp-308h] BYREF
  char v71[12]; // [esp+4F4h] [ebp-304h] BYREF
  int v72; // [esp+500h] [ebp-2F8h]
  const wchar_t *v73; // [esp+75Ch] [ebp-9Ch]
  __int16 v74; // [esp+760h] [ebp-98h]
  int v75; // [esp+770h] [ebp-88h]
  char v76[16]; // [esp+7D4h] [ebp-24h] BYREF
  int *v77; // [esp+7E8h] [ebp-10h]
  int v78; // [esp+7F4h] [ebp-4h]

  v77 = &v36;
  v65 = this;
  v59 = a2;
  v52 = (int *)((char *)a2 + 8);
  v67 = *((_DWORD *)a2 + 2);
  v53 = this;
  v69[1] = 0;
  ComponentLicenseLevel = ChlpLicenseManager::GetComponentLicenseLevel((char *)this + 332, 404, 1);
  v3 = 1;
  CdlgWait::CdlgWait((CdlgWait *)v37, 0);
  v46 = &CMap<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,wchar_t const *,short,short>::`vftable';
  v47 = 0;
  v48 = 17;
  v49 = xmmword_11137850;
  v78 = 1;
  if ( ComponentLicenseLevel != 1 )
  {
    if ( ComponentLicenseLevel == 2 )
    {
      v3 = 2;
    }
    else if ( ComponentLicenseLevel == 3 )
    {
      v3 = 12;
    }
  }
  v4 = *(_DWORD *)v65;
  v66 = v3;
  v37[516] = 0;
  v5 = (*(int (__thiscall **)(CappMain *, const wchar_t *, const wchar_t *, _DWORD))(v4 + 132))(
         v65,
         L"Export Settings",
         L"Export Import Cores",
         0);
  if ( v5 > 0 )
  {
    if ( v5 < v3 )
      v3 = v5;
    v66 = v3;
  }
  CdbControl::RefreshIfIll(0, 0);
  ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(v69);
  v6 = 0;
  LOBYTE(v78) = 2;
  LOBYTE(v63) = 1;
  while ( 1 )
  {
    v58 = v6;
    if ( v6 >= v67 )
      break;
    if ( v6 < 0 || v6 >= *v52 )
      goto LABEL_76;
    ChlpPath::ChlpPath((ChlpPath *)&v70, *(const wchar_t **)(*((_DWORD *)v59 + 1) + 4 * v6));
    LOBYTE(v78) = 4;
    Extension = ChlpPath::GetExtension(&v70, v51);
    LOBYTE(v78) = 5;
    v7 = sub_11120810(Extension);
    LOBYTE(v78) = 4;
    v8 = v7;
    ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v51);
    switch ( v8 )
    {
      case 3:
        DocTemplateByAnalysisMode = CappMainBase::GetDocTemplateByAnalysisMode(v65, 4);
        if ( DocTemplateByAnalysisMode )
        {
          v10 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(int))(*(_DWORD *)DocTemplateByAnalysisMode + 180))(DocTemplateByAnalysisMode);
          if ( v10 )
            CengSignalFilesMinimal::TestSlotAvail(v10, 1);
          sub_111200E0(&v70);
          LOBYTE(v78) = 6;
          v44[0] = (int)v38;
          v44[1] = 1000 * (v6 + 1) / v67;
          v11 = sub_11120820(v44, 3, v63);
          v12 = (wchar_t **)&v60;
          goto LABEL_19;
        }
        ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v64);
        LOBYTE(v78) = 7;
        v34 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v50, 4);
        LOBYTE(v78) = 8;
        ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v64, 23735, v70, v34);
        LOBYTE(v78) = 7;
        ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v50);
        v13 = AfxMessageBox(v64, 0x4031u, 0x5CB7u);
        v14 = &v64;
        if ( v13 != 1 )
          goto LABEL_23;
LABEL_22:
        ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v14);
        ChlpPath::~ChlpPath((ChlpPath *)&v70);
        LOBYTE(v78) = 2;
        ++v6;
        break;
      case 4:
        v15 = CappMainBase::GetDocTemplateByAnalysisMode(v65, 4);
        if ( !v15 )
        {
          ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v62);
          LOBYTE(v78) = 10;
          v35 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v57, 4);
          LOBYTE(v78) = 11;
          ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v62, 23735, v70, v35);
          LOBYTE(v78) = 10;
          ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v57);
          v17 = AfxMessageBox(v62, 0x4031u, 0x5CB7u);
          v14 = &v62;
          if ( v17 != 1 )
          {
LABEL_23:
            ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v14);
LABEL_51:
            ChlpPath::~ChlpPath((ChlpPath *)&v70);
            LOBYTE(v78) = 2;
            goto LABEL_52;
          }
          goto LABEL_22;
        }
        v16 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(int))(*(_DWORD *)v15 + 180))(v15);
        if ( v16 )
          CengSignalFilesMinimal::TestSlotAvail(v16, 1);
        sub_111200E0(&v70);
        LOBYTE(v78) = 9;
        v43[0] = (int)v38;
        v43[1] = 1000 * (v6 + 1) / v67;
        v11 = sub_11120820(v43, 4, v63);
        v12 = &v68;
LABEL_19:
        if ( v11 )
        {
          LOBYTE(v63) = 0;
          ChlpPath::~ChlpPath((ChlpPath *)v12);
          goto LABEL_70;
        }
        ChlpPath::~ChlpPath((ChlpPath *)v12);
        ChlpPath::~ChlpPath((ChlpPath *)&v70);
        LOBYTE(v78) = 2;
        ++v6;
        break;
      case 1:
        CFile::CFile((CFile *)v42, v70, 0x8020u);
        LOBYTE(v78) = 12;
        ChlpImportExam::ChlpImportExam((ChlpImportExam *)v71, (struct CFile *)v42, v66);
        LOBYTE(v78) = 13;
        v18 = (struct CdtCardioMg *)CappMainBase::GetDocTemplateByAnalysisMode(v65, v75);
        if ( v18 )
        {
          v19 = (CengSignalFilesMinimal *)(*(int (__thiscall **)(struct CdtCardioMg *))(*(_DWORD *)v18 + 180))(v18);
          if ( v19 )
            CengSignalFilesMinimal::TestSlotAvail(v19, 1);
          v45[0] = (int)v38;
          v45[1] = 1000 * (v6 + 1) / v67;
          if ( ChlpImportExam::Read((ChlpImportExam *)v71, v18, (const struct ChlpNewNestedJob *)v45) )
          {
            if ( !*((_DWORD *)v73 - 3) )
            {
              AfxMessageBox(L"No ExamSetUUID", 0, 0);
              goto LABEL_39;
            }
            CsUuid::CsUuid((CsUuid *)v76, v73);
            StringRepresentation = CsUuid::GetStringRepresentation(v76, v56, 0, 1);
            LOBYTE(v78) = 14;
            ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
              v69,
              StringRepresentation);
            LOBYTE(v78) = 13;
            ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v56);
            (*(void (__thiscall **)(struct CdtCardioMg *, int))(*(_DWORD *)v18 + 216))(v18, v72);
            v55 = v69[0];
            v21 = CMapStringToPtr::HashKey(v20, v69[0]);
            if ( v47 )
            {
              for ( i = *(_DWORD *)(v47 + 4 * (v21 % v48)); i; i = *(_DWORD *)(i + 8) )
              {
                if ( *(_DWORD *)(i + 12) == v21
                  && !ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Compare(i, v55) )
                {
                  goto LABEL_47;
                }
              }
            }
            v23 = v74;
            *(_WORD *)sub_11102240(v69[0]) = v23;
          }
LABEL_47:
          ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71);
          CFile::~CFile((CFile *)v42);
LABEL_70:
          v78 = 2;
          ChlpPath::~ChlpPath((ChlpPath *)&v70);
          v6 = v58 + 1;
        }
        else
        {
          ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v61);
          LOBYTE(v78) = 15;
          v32 = *(_DWORD *)CarcDiskHeaderAbs::GetAnalysisModeName(v54, v75);
          LOBYTE(v78) = 16;
          ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v61, 23735, v70, v32);
          LOBYTE(v78) = 15;
          ((void (__thiscall *)(char *))CMFCRibbonInfo::XID::~XID)(v54);
          if ( AfxMessageBox(v61, 0x4031u, 0x5CB7u) != 1 )
          {
            ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v61);
            ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71);
            CFile::~CFile((CFile *)v42);
            goto LABEL_51;
          }
          ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v61);
LABEL_39:
          ChlpImportExam::~ChlpImportExam((ChlpImportExam *)v71);
          CFile::~CFile((CFile *)v42);
          ChlpPath::~ChlpPath((ChlpPath *)&v70);
          LOBYTE(v78) = 2;
          ++v6;
        }
        break;
      default:
        goto LABEL_70;
    }
  }
LABEL_52:
  CtbExams::CtbExams((CtbExams *)v39, 0);
  LOBYTE(v78) = 28;
  v24 = -((_DWORD)v49 != 0);
  if ( (_DWORD)v49 )
  {
    while ( v47 )
    {
      v25 = v24;
      if ( !v24 )
        break;
      if ( v24 == -1 )
      {
        v26 = 0;
        if ( v48 )
        {
          while ( 1 )
          {
            v25 = *(_DWORD *)(v47 + 4 * v26);
            if ( v25 )
              break;
            if ( ++v26 >= v48 )
              goto LABEL_76;
          }
        }
      }
      v24 = *(_DWORD *)(v25 + 8);
      if ( !v24 )
      {
        for ( j = *(_DWORD *)(v25 + 12) % v48 + 1; j < v48; ++j )
        {
          v24 = *(_DWORD *)(v47 + 4 * j);
          if ( v24 )
            break;
        }
      }
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v69, v25);
      v28 = *(_WORD *)(v25 + 4);
      CtbExams::SetFilterForScanExamSet((CtbExams *)v39, v69[0], 0);
      CsetBase::Open((CsetBase *)v39, 0);
      ExamCount = CtbExams::GetExamCount((CtbExams *)v39);
      if ( v28 <= 0 || v41[0] == -1 )
      {
        v30 = 0;
        while ( !v40 )
        {
          CsetBase::Edit((CsetBase *)v39);
          v41[0] = v30++;
          CRecordset::SetFieldNull((CRecordset *)v39, v41, 0);
          CsetBase::Update((CsetBase *)v39);
          (*(void (__thiscall **)(int *, int, int))(v39[0] + 28))(v39, 1, 1);
        }
        CRecordset::Close((CRecordset *)v39);
        CtbExamsSets::UpdateExamCount(v69[0], v30);
      }
      else
      {
        if ( ExamCount > 0 && ExamCount != v28 )
        {
          ((void (__thiscall *)(wchar_t **))ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>)(&v68);
          LOBYTE(v78) = 29;
          ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Format(&v68, 23729, v28, ExamCount);
          AfxMessageBox(v68, 0, 0);
          LOBYTE(v78) = 28;
          ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(&v68);
        }
        CRecordset::Close((CRecordset *)v39);
      }
      if ( !v24 )
        goto LABEL_75;
    }
LABEL_76:
    AfxThrowInvalidArgException();
  }
LABEL_75:
  CtbExams::~CtbExams((CtbExams *)v39);
  ((void (__thiscall *)(wchar_t **))CMFCRibbonInfo::XID::~XID)(v69);
  LOBYTE(v78) = 30;
  v46 = &CMap<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,wchar_t const *,short,short>::`vftable';
  sub_11107EC0(&v46);
  CdlgWait::~CdlgWait((CdlgWait *)v37);
}

Код CappMain::ImportExam проясняет общую логику: программа не просто открывает .xcm, она выполняет полноценный импорт данных в СУБД. Судя по вызовам классов CsetBase и методам UpdateExamCount, файл парсится, и его содержимое раскладывается по таблицам базы данных.

К слову, программа при установке предлагает выбор пользователю из нескольких СУБД. Я выбрал MySQL.

Хотя программа и импортирует данные в СУБД лезть в базу у меня не было нужды. Моя задача — выяснить, что откуда и как читается из файла, а не то, как оно потом записывается в таблицы.

Основную логику импорта выполняет класс ChlpImportExam из модуля ModulesBase.dll.

Конструктор ChlpImportExam
ChlpImportExam *__thiscall ChlpImportExam::ChlpImportExam(ChlpImportExam *this, struct CFile *a2, void *a3)
{
  CArchive *v4; // eax
  CArchive *v5; // eax
  _DWORD v7[8]; // [esp+0h] [ebp-2Ch] BYREF
  int v8; // [esp+28h] [ebp-4h]

  v7[7] = v7;
  v7[6] = this;
  CarcDiskHeaderFD::CarcDiskHeaderFD(this, 0);
  v8 = 0;
  *(_DWORD *)this = &ChlpImportExam::`vftable';
  CtypVariable::CtypVariable((ChlpImportExam *)((char *)this + 672));
  LOBYTE(v8) = 1;
  *((_DWORD *)this + 182) = a3;
  v4 = (CArchive *)operator new(0x48u);
  LOBYTE(v8) = 2;
  if ( v4 )
    v5 = CArchive::CArchive(v4, a2, 1u, 4096, 0);
  else
    v5 = 0;
  *((_DWORD *)this + 1) = v5;
  LOBYTE(v8) = 3;
  CarcDiskHeaderAbs::Read(this);
  return this;
}

Как видно из листинга, файл .xcm представляет собой архив, созданный стандартным классом MFC CArchive. Это означает, что данные внутри организованы не как простой поток байтов, а как иерархия сериализованных объектов.

Судя по вызову CarcDiskHeaderAbs::Read, первым делом из архива вычитывается заголовок файла. Вероятно, CarcDiskHeaderFD является наследником CarcDiskHeaderAbs. К слову, оба этих класса находятся в модуле Serializer.dll.

Конструктор CarcDiskHeaderFD:

CarcDiskHeaderFD *__thiscall CarcDiskHeaderFD::CarcDiskHeaderFD(
        CarcDiskHeaderFD *this,
        int (__stdcall ***a2)(int, int))
{
  CarcDiskHeaderAbs::CarcDiskHeaderAbs(this, a2);
  *(_DWORD *)this = &CarcDiskHeaderFD::`vftable';
  ChlpPath::ChlpPath((CarcDiskHeaderFD *)((char *)this + 668));
  return this;
}

Судя по коду, выходит, что CarcDiskHeaderFD действительно является наследником CarcDiskHeaderAbs

Метод CarcDiskHeaderAbs::Read
int __thiscall CarcDiskHeaderAbs::Read(CarcDiskHeaderAbs *this)
{
  int v2; // esi
  int v3; // ecx
  unsigned int v4; // edx
  int v5; // ecx
  bool v6; // zf
  unsigned int v7; // edx
  char v8; // al
  CarcDiskHeaderAbs *v9; // ecx
  const wchar_t *v11; // eax
  const wchar_t *v12; // eax

  v2 = *((_DWORD *)this + 1);
  if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
  {
    v11 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v11);
  }
  v3 = *(_DWORD *)(v2 + 40);
  v4 = *(_DWORD *)(v2 + 44);
  if ( v3 + 1 > v4 )
    CArchive::FillBuffer((CArchive *)v2, v3 - v4 + 1);
  *((_BYTE *)this + 8) = **(_BYTE **)(v2 + 40);
  v5 = *(_DWORD *)(v2 + 40) + 1;
  v6 = (*(_BYTE *)(v2 + 24) & 1) == 0;
  *(_DWORD *)(v2 + 40) = v5;
  if ( v6 )
  {
    v12 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v12);
  }
  v7 = *(_DWORD *)(v2 + 44);
  if ( v5 + 1 > v7 )
    CArchive::FillBuffer((CArchive *)v2, v5 - v7 + 1);
  *((_BYTE *)this + 9) = *(_BYTE *)(*(_DWORD *)(v2 + 40))++;
  v8 = *((_BYTE *)this + 8);
  if ( v8 == 48 )
  {
    v9 = this;
    if ( (unsigned __int16)(((*((unsigned __int8 *)this + 9) << 8) | 0x30) - 12592) <= 0xA00u )
      return CarcDiskHeaderAbs::ReadMAPA(this);
  }
  else
  {
    v9 = this;
    if ( v8 == 49 )
      return CarcDiskHeaderAbs::ReadHolter(this);
  }
  return (*(int (__thiscall **)(CarcDiskHeaderAbs *, int))(*(_DWORD *)this + 40))(v9, 7);
}

Тут читается тип данных: либо это MAPA (тип 48) — суточное давление, либо Holter (тип 49). Суточное давление, понятное дело, мне не нужно — меня интересует только метод CarcDiskHeaderAbs::ReadHolter.

Метод CarcDiskHeaderAbs::ReadHolter
int __thiscall CarcDiskHeaderAbs::ReadHolter(CarcDiskHeaderAbs *this)
{
  int v2; // esi
  int v3; // ecx
  unsigned int v4; // edx
  int v5; // ecx
  bool v6; // zf
  unsigned int v7; // edx
  char *v8; // eax
  char v9; // cl
  int v11; // ecx
  unsigned int v12; // edx
  unsigned __int16 *v13; // eax
  int v14; // ecx
  int v15; // ecx
  unsigned int v16; // edx
  unsigned __int16 *v17; // eax
  int v18; // ecx
  int v19; // ecx
  unsigned int v20; // edx
  bool v21; // al
  int v22; // ecx
  unsigned int v23; // edx
  const wchar_t *v24; // eax
  const wchar_t *v25; // eax
  const wchar_t *v26; // eax
  const wchar_t *v27; // eax
  const wchar_t *v28; // eax
  const wchar_t *v29; // eax
  int v30; // [esp+Ch] [ebp-10h] BYREF
  int v31; // [esp+18h] [ebp-4h]

  v2 = *((_DWORD *)this + 1);
  if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
  {
    v24 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v24);
  }
  v3 = *(_DWORD *)(v2 + 40);
  v4 = *(_DWORD *)(v2 + 44);
  if ( v3 + 1 > v4 )
    CArchive::FillBuffer((CArchive *)v2, v3 - v4 + 1);
  *((_BYTE *)this + 612) = **(_BYTE **)(v2 + 40);
  v5 = *(_DWORD *)(v2 + 40) + 1;
  v6 = (*(_BYTE *)(v2 + 24) & 1) == 0;
  *(_DWORD *)(v2 + 40) = v5;
  if ( v6 )
  {
    v25 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v25);
  }
  v7 = *(_DWORD *)(v2 + 44);
  if ( v5 + 1 > v7 )
    CArchive::FillBuffer((CArchive *)v2, v5 - v7 + 1);
  v8 = *(char **)(v2 + 40);
  v9 = *v8;
  *(_DWORD *)(v2 + 40) = v8 + 1;
  if ( v9 != 26 )
    return (*(int (__thiscall **)(CarcDiskHeaderAbs *, int))(*(_DWORD *)this + 40))(this, 5);
  ReadString((CArchive *)v2, (int)this + 624); // Номер исследования
  ReadString((CArchive *)v2, (int)this + 12); // ID исследования
  ReadString((CArchive *)v2, (int)this + 20); // Имя и фамилия врача-аналитика
  ReadString((CArchive *)v2, (int)this + 24); // Имя пациента
  ReadString((CArchive *)v2, (int)this + 28); // Фамилия пациента 
  CarcDiskHeaderAbs::ReadFix(this, (CarcDiskHeaderAbs *)((char *)this + 32)); // Читает дату и время исследования
  ReadString((CArchive *)v2, (int)this + 44); // Имя и фамилия лечащего врача
  ReadString((CArchive *)v2, (int)this + 48); // Офис установки холтера
  ReadString((CArchive *)v2, (int)this + 632); // Название холтера
  if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
  {
    v26 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v26);
  }
  v11 = *(_DWORD *)(v2 + 40);
  v12 = *(_DWORD *)(v2 + 44);
  if ( v11 + 2 > v12 )
    CArchive::FillBuffer((CArchive *)v2, v11 - v12 + 2);
  v13 = *(unsigned __int16 **)(v2 + 40);
  v14 = *v13;
  *(_DWORD *)(v2 + 40) = v13 + 1;
  *((_DWORD *)this + 159) = v14;
  if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
  {
    v27 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v27);
  }
  v15 = *(_DWORD *)(v2 + 40);
  v16 = *(_DWORD *)(v2 + 44);
  if ( v15 + 2 > v16 )
    CArchive::FillBuffer((CArchive *)v2, v15 - v16 + 2);
  v17 = *(unsigned __int16 **)(v2 + 40);
  v18 = *v17;
  *(_DWORD *)(v2 + 40) = v17 + 1;
  *((_DWORD *)this + 160) = v18;
  if ( v18 == 4 || v18 == 7 )
    *((_DWORD *)this + 160) = 1;
  if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
  {
    v28 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
    AfxThrowArchiveException(4, v28);
  }
  v19 = *(_DWORD *)(v2 + 40);
  v20 = *(_DWORD *)(v2 + 44);
  if ( v19 + 2 > v20 )
    CArchive::FillBuffer((CArchive *)v2, v19 - v20 + 2);
  *((_WORD *)this + 322) = **(_WORD **)(v2 + 40);
  *(_DWORD *)(v2 + 40) += 2;
  if ( *((_BYTE *)this + 8) == 48 )
    v21 = ((unsigned __int16)(*((unsigned __int8 *)this + 9) << 8) | 0x30u) > 0x3730;
  else
    v21 = CarcDiskHeaderAbs::GetExportVersion(this) >= 3;
  if ( v21 )
  {
    ReadString((CArchive *)v2, (int)this + 616); // Контрольная сумма
    if ( (*(_BYTE *)(v2 + 24) & 1) == 0 )
    {
      v29 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v2 + 20);
      AfxThrowArchiveException(4, v29);
    }
    v22 = *(_DWORD *)(v2 + 40);
    v23 = *(_DWORD *)(v2 + 44);
    if ( v22 + 2 > v23 )
      CArchive::FillBuffer((CArchive *)v2, v22 - v23 + 2);
    *((_WORD *)this + 314) = **(_WORD **)(v2 + 40);
    *(_DWORD *)(v2 + 40) += 2;
    if ( *((_WORD *)this + 314) == 32484 )
      *((_WORD *)this + 314) = -1;
    if ( CarcDiskHeaderAbs::GetExportVersion(this) >= 4 )
    {
      ReadString((CArchive *)v2, (int)this + 52); // Страховой полис
      if ( CarcDiskHeaderAbs::GetExportVersion(this) >= 5 )
      {
        ReadString((CArchive *)v2, (int)this + 16); // JSON с информацией о холтере (сжатый в zlib и закодированный в base64) 
        ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v30);
        v31 = 0;
        ReadCString((CArchive *)v2, (int)&v30);
        CmtypTrackingInfo::SetEncodedString((CarcDiskHeaderAbs *)((char *)this + 56), (const char *)v30);
        v31 = -1;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v30);
      }
    }
  }
  CarcDiskHeaderAbs::CheckData(this);
  return 1;
}

Судя по этому коду, из заголовка xcm-файла читаются:

  • Номер исследования

  • ID исследования

  • Имя и фамилия врача-аналитика

  • Имя пациента

  • Фамилия пациента

  • Дата и время исследования

  • Имя и фамилия лечащего врача

  • Офис установки холтера

  • Название холтера

В зависимости от версии также читаются:

  • Контрольная сумма

  • Страховой полис

  • JSON с информацией о холтере (сжатый в zlib и закодированный в base64)  

Заголовок xcm-файла
00000000   31 35 30 1A FF FE FF 07  48 00 20 00 34 00 36 00  33 00 31 00 32 00 09 31  51 33 2D 33   150 яюя H   4 6 3 1 2  1Q3-3
00000028   35 38 39 30 FF FE FF 01  2E 00 FF FE FF 13 41 00  6C 00 63 00 65 00 75 00  20 00 4C 00   5890яюя . яюя A l c e u   L 
00000056   75 00 69 00 7A 00 20 00  64 00 61 00 20 00 53 00  69 00 6C 00 76 00 61 00  FF FE FF 07   u i z   d a   S i l v a яюя 
00000084   4D 00 61 00 72 00 74 00  69 00 6E 00 73 00 E9 07  06 00 11 00 09 00 21 00  00 00 FF FE   M a r t i n s й       !   яю
00000112   FF 1F 44 00 72 00 2E 00  20 00 52 00 6F 00 64 00  72 00 69 00 67 00 6F 00  20 00 56 00   я D r .   R o d r i g o   V 
00000140   61 00 6C 00 65 00 6E 00  74 00 65 00 20 00 52 00  61 00 6D 00 6F 00 73 00  20 00 52 00   a l e n t e   R a m o s   R 
00000168   6F 00 63 00 68 00 61 00  FF FE FF 07 43 00 6C 00  69 00 6E 00 69 00 63 00  61 00 FF FE   o c h a яюя C l i n i c a яю
00000196   FF 12 48 00 6F 00 6C 00  74 00 65 00 72 00 20 00  64 00 65 00 20 00 33 00  20 00 63 00   я H o l t e r   d e   3   c 
00000224   61 00 6E 00 61 00 69 00  73 00 04 00 08 00 05 00  20 37 46 31 45 44 33 36  36 34 44 30   a n a i s        7F1ED3664D0
00000252   38 34 38 43 35 39 34 33  38 36 44 39 34 45 36 41  33 44 33 39 32 00 00 FF  FE FF 00 00   848C594386D94E6A3D392  яюя  
00000280   FF 34 01 65 4A 78 46 6A  38 31 71 77 6C 41 51 68  66 64 39 43 70 6C 31 54  4F 39 66 6F   я4 eJxFj81qwlAQhfd9Cpl1TO9fo
00000308   73 6E 4F 4A 6F 6F 42 63  53 50 59 39 61 41 44 48  63 67 66 6B 32 75 74 6C  62 35 37 6B   snOJooBcSPY9aADHcgfk2utlb57k
00000336   78 74 6F 34 53 37 75 4F  64 39 68 35 73 77 54 4E  69 33 57 6A 32 2B 53 71  6F 54 63 72   xto4S7uOd9h5swTNi3Wj2+SqoTcr
00000364   56 31 71 72 46 6E 70 43  44 5A 39 58 37 57 44 78  37 71 65 41 45 42 77 6A  74 6A 51 4B   V1qrFnpCDZ9X7WDx7qeAEBwjtjQK
00000392   41 71 55 4B 33 64 48 38  6F 75 69 5A 6D 72 39 44  4D 38 6B 49 30 74 6A 75  37 61 78 69   AqUK3dH8ouiZmr9DM8kI0tju7axi
00000420   59 30 5A 33 54 66 30 56  63 76 2B 6A 44 58 6B 4B  73 6A 77 31 53 5A 78 45  5A 54 30 79   Y0Z3Tf0Vcv+jDXkKsjw1SZxEZT0y
00000448   52 66 61 66 76 55 73 36  4C 6C 72 35 79 32 7A 65  79 4C 68 4B 51 6D 4A 79  6B 7A 32 71   RfafvUs6Llr5y2zeyLhKQmJykz2q
00000476   70 33 54 66 2B 78 64 32  4E 4E 57 70 4A 4D 68 54  4E 32 78 4E 48 63 55 4B  74 46 50 35   p3Tf+xd2NNWpJMhTN2xNHcUKtFP5
00000504   59 7A 53 64 71 6E 56 55  72 6D 46 55 6E 6C 34 38  42 2B 61 53 2B 70 59 32  39 48 63 33   YzSdqnVUrmFUnl48B+aS+pY29Hc3
00000532   38 4E 74 4F 6C 75 70 53  52 37 34 41 76 6B 54 74  4E 56 6A 78 5A 2B 67 44  39 79 77 4C   8NtOlupSR74AvkTtNVjxZ+gD9ywL
00000560   2F 45 42 65 5A 6F 6B 4E  6F 6E 67 39 48 48 7A 31  77 4A 76 41 30 32 5A 6C  31 39 34 78   /EBeZokNong9HHz1wJvA02Zl194x
00000588   6C 75 7A                                                                                 luz

Первый байт данных определяет тип данных: MAPA (0x30) или Holter (0x31). Следующие два байта задают мажорную и минорную версии xcm-файла соответственно, а далее идут поля, указанные выше.

Затем идут данные врача-аналитика, которые читаются методом CarctAnalyst::Load

Метод CarctAnalyst::Load
void __thiscall CarctAnalyst::Load(CarctAnalyst *this, unsigned int a2)
{
  CarcDiskHeaderAbs *v3; // esi
  int v4; // edi
  int v5; // eax
  char *v6; // ecx
  int v7; // ecx
  unsigned int v8; // edx
  unsigned __int8 *v9; // eax
  unsigned __int8 *v10; // edx
  unsigned int v11; // eax
  unsigned int v12; // ecx
  unsigned int v13; // esi
  unsigned __int16 v14; // ax
  int v15; // eax
  unsigned int v16; // eax
  int v17; // eax
  int v18; // eax
  int v19; // eax
  int v20; // eax
  int v21; // eax
  int v22; // eax
  int v23; // eax
  int v24; // eax
  int v25; // eax
  int v26; // eax
  int v27; // eax
  int v28; // eax
  int v29; // eax
  int v30; // eax
  int v31; // eax
  int v32; // eax
  int v33; // eax
  int v34; // eax
  int v35; // eax
  int v36; // eax
  int v37; // eax
  int v38; // eax
  int v39; // eax
  int v40; // eax
  int v41; // eax
  int v42; // eax
  int v43; // eax
  int v44; // eax
  int v45; // eax
  int v46; // eax
  char *v47; // ecx
  int v48; // eax
  int v49; // eax
  int v50; // eax
  int v51; // eax
  int v52; // eax
  int v53; // eax
  int v54; // eax
  int v55; // eax
  int v56; // eax
  int v57; // eax
  int v58; // eax
  int v59; // eax
  int v60; // eax
  int v61; // eax
  const wchar_t *v62; // eax
  const wchar_t *v63; // eax
  int v64; // eax
  int v65; // eax
  const wchar_t *v66; // eax
  const wchar_t *v67; // eax
  int v68; // [esp-8h] [ebp-38h]
  int v69; // [esp-8h] [ebp-38h]
  int v70; // [esp-8h] [ebp-38h]
  int v71; // [esp-8h] [ebp-38h]
  int v72; // [esp-8h] [ebp-38h]
  int v73; // [esp-8h] [ebp-38h]
  int v74; // [esp-8h] [ebp-38h]
  int v75; // [esp-8h] [ebp-38h]
  int v76; // [esp-8h] [ebp-38h]
  int v77; // [esp-8h] [ebp-38h]
  int v78; // [esp-8h] [ebp-38h]
  int v79; // [esp-8h] [ebp-38h]
  int v80; // [esp-8h] [ebp-38h]
  int v81; // [esp-8h] [ebp-38h]
  int v82; // [esp-4h] [ebp-34h]
  int v83; // [esp-4h] [ebp-34h]
  int v84; // [esp-4h] [ebp-34h]
  int v85; // [esp-4h] [ebp-34h]
  int v86; // [esp-4h] [ebp-34h]
  int v87; // [esp-4h] [ebp-34h]
  int v88; // [esp-4h] [ebp-34h]
  int v89; // [esp-4h] [ebp-34h]
  int v90; // [esp-4h] [ebp-34h]
  int v91; // [esp-4h] [ebp-34h]
  int v92; // [esp-4h] [ebp-34h]
  int v93; // [esp-4h] [ebp-34h]
  int v94; // [esp-4h] [ebp-34h]
  int v95; // [esp-4h] [ebp-34h]
  int v96; // [esp-4h] [ebp-34h]
  char v97[4]; // [esp+10h] [ebp-20h] BYREF
  char v98[4]; // [esp+14h] [ebp-1Ch] BYREF
  char v99[4]; // [esp+18h] [ebp-18h] BYREF
  int v100; // [esp+1Ch] [ebp-14h]
  int v101; // [esp+20h] [ebp-10h] BYREF
  int v102; // [esp+2Ch] [ebp-4h]

  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v101);
  v3 = (CarcDiskHeaderAbs *)a2;
  v102 = 0;
  v4 = *(_DWORD *)(a2 + 4);
  if ( *(_BYTE *)(a2 + 8) != 48 )
  {
    v5 = *(_DWORD *)(a2 + 20);
    v6 = (char *)(a2 + 20);
    a2 = 48;
    if ( !*(_DWORD *)(v5 - 12) )
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v6, L"???");
    if ( CarcDiskHeaderAbs::IsNewerVersion(v3) )
    {
      v60 = sub_5C622FB0((_DWORD *)v4);
      v61 = (*(int (__thiscall **)(int, unsigned int *))(*(_DWORD *)v60 + 32))(v60, &a2);
      LOBYTE(v102) = 1;
      v62 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v61);
      AfxThrowArchiveException(7, v62);
    }
    if ( CarcDiskHeaderAbs::GetExportVersion(v3) > 1 )
    {
      if ( (*(_BYTE *)(v4 + 24) & 1) == 0 )
      {
        v63 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v4 + 20);
        AfxThrowArchiveException(4, v63);
      }
      v7 = *(_DWORD *)(v4 + 40);
      v8 = *(_DWORD *)(v4 + 44);
      if ( v7 + 1 > v8 )
        CArchive::FillBuffer((CArchive *)v4, v7 - v8 + 1);
      v9 = *(unsigned __int8 **)(v4 + 40);
      v10 = v9 + 1;
      v11 = *v9;
      *(_DWORD *)(v4 + 40) = v10;
      a2 = v11;
      if ( v11 > 0x35 )
      {
        v64 = sub_5C622FB0((_DWORD *)v4);
        v65 = (*(int (__thiscall **)(int, unsigned int *))(*(_DWORD *)v64 + 32))(v64, &a2);
        LOBYTE(v102) = 2;
        v66 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v65);
        AfxThrowArchiveException(7, v66);
      }
      if ( v11 - 50 <= 2 )
      {
        if ( (*(_BYTE *)(v4 + 24) & 1) == 0 )
        {
          v67 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v4 + 20);
          AfxThrowArchiveException(4, v67);
        }
        v12 = *(_DWORD *)(v4 + 44);
        if ( (unsigned int)(v10 + 4) > v12 )
          CArchive::FillBuffer((CArchive *)v4, (unsigned int)&v10[-v12 + 4]);
        *(_DWORD *)(v4 + 40) += 4;
      }
    }
    ReadString((CArchive *)v4, (int)this + 8); // Имя врача-аналитика
    if ( !*(_DWORD *)(*((_DWORD *)this + 2) - 12) )
      CmtypAnalyst::SetDefaultAnalystName(this, 0);
    ReadString((CArchive *)v4, (int)this + 12); // ID врача-аналитика
    ReadString((CArchive *)v4, (int)this + 16); // Тип ID врача-аналитика
    ReadString((CArchive *)v4, (int)this + 20); // Офис врача-аналитика
    ReadString((CArchive *)v4, (int)this + 24); // Адрес врача-а��алитика
    ReadString((CArchive *)v4, (int)this + 28); // Уровень компетенции врача-аналитика
    ReadString((CArchive *)v4, (int)this + 32); // Почтовый индекс врача-аналитика
    ReadString((CArchive *)v4, (int)this + 36); // Город врача-аналитика
    ReadString((CArchive *)v4, (int)this + 40); // Штат врача-аналитика
    v13 = a2;
    if ( a2 < 0x34 )
    {
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
        (char *)this + 44,
        &word_5C659A40);
      ReadString((CArchive *)v4, (int)this + 48);
    }
    else
    {
      ReadString((CArchive *)v4, (int)this + 44); // Страна врача-аналитика
      ReadString((CArchive *)v4, (int)this + 48);
      if ( v13 >= 0x35 )
        goto LABEL_22;
    }
    ReadString((CArchive *)v4, (int)&v101);
LABEL_22:
    if ( v13 < 0x33 )
    {
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
        (char *)this + 52,
        &word_5C659A40);
      ReadString((CArchive *)v4, (int)this + 60);
      if ( v13 < 0x31 )
        ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
          (char *)this + 56,
          &word_5C659A40);
      else
        ReadString((CArchive *)v4, (int)this + 56);
    }
    else
    {
      ReadString((CArchive *)v4, (int)this + 52);
      ReadString((CArchive *)v4, (int)this + 60);
      ReadString((CArchive *)v4, (int)this + 56);
    }
    goto LABEL_39;
  }
  v14 = (*(unsigned __int8 *)(a2 + 9) << 8) | 0x30;
  v100 = v14;
  if ( v14 != 12592 )
  {
    ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&a2);
    v15 = *(_DWORD *)this;
    LOBYTE(v102) = 3;
    (*(void (__thiscall **)(CarctAnalyst *))(v15 + 24))(this);
    ReadString((CArchive *)v4, (int)&a2);
    v16 = a2;
    if ( !*(_DWORD *)(a2 - 12) )
    {
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(&a2, L"-");
      v16 = a2;
    }
    v17 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v98,
            v16);
    LOBYTE(v102) = 4;
    v18 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v17, v99, 50);
    v19 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v18, v68, v82);
    LOBYTE(v102) = 5;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 8, v19);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    ReadString((CArchive *)v4, (int)&a2);
    v20 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 6;
    v21 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v20, v98, 12);
    v22 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v21, v69, v83);
    LOBYTE(v102) = 7;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 12, v22);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v23 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 8;
    v24 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v23, v98, 12);
    v25 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v24, v70, v84);
    LOBYTE(v102) = 9;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 16, v25);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v26 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 10;
    v27 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v26, v98, 50);
    v28 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v27, v71, v85);
    LOBYTE(v102) = 11;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 20, v28);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v29 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 12;
    v30 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v29, v98, 50);
    v31 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v30, v72, v86);
    LOBYTE(v102) = 13;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 24, v31);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v32 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 14;
    v33 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v32, v98, 50);
    v34 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v33, v73, v87);
    LOBYTE(v102) = 15;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 28, v34);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v35 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 16;
    v36 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v35, v98, 10);
    v37 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v36, v74, v88);
    LOBYTE(v102) = 17;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 32, v37);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    ReadString((CArchive *)v4, (int)&a2);
    v38 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v99,
            a2);
    LOBYTE(v102) = 18;
    v39 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v38, v98, 30);
    v40 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v39, v75, v89);
    LOBYTE(v102) = 19;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 36, v40);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
    if ( (unsigned __int16)v100 <= 0x3530u )
    {
      ReadString((CArchive *)v4, (int)&a2);
      v92 = *(_DWORD *)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(&a2, v97, 3);
      LOBYTE(v102) = 22;
      v48 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
              v99,
              v92);
      LOBYTE(v102) = 23;
      v49 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v48, v98, 30);
      v50 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v49, v78, v93);
      LOBYTE(v102) = 24;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 40, v50);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
      v47 = v97;
    }
    else
    {
      ReadString((CArchive *)v4, (int)&a2);
      v41 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
              v99,
              a2);
      LOBYTE(v102) = 20;
      v42 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v41, v98, 30);
      v43 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v42, v76, v90);
      LOBYTE(v102) = 21;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 40, v43);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
      LOBYTE(v102) = 3;
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v99);
      ReadString((CArchive *)v4, (int)&a2);
      v44 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
              v99,
              a2);
      LOBYTE(v102) = 25;
      v45 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v44, v98, 30);
      v46 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v45, v77, v91);
      LOBYTE(v102) = 26;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 44, v46);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
      v47 = v99;
    }
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v47);
    ReadString((CArchive *)v4, (int)&a2);
    v51 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            v98,
            a2);
    LOBYTE(v102) = 27;
    v52 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v51, v97, 18);
    v53 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v52, v79, v94);
    LOBYTE(v102) = 28;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 48, v53);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97);
    LOBYTE(v102) = 3;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    if ( *((_WORD *)v3 + 4) < 0x3B30u )
      ReadString((CArchive *)v4, (int)&v101);
    if ( (unsigned __int16)v100 <= 0x3530u )
    {
      ReadString((CArchive *)v4, (int)&a2);
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 60, a2);
    }
    else
    {
      ReadString((CArchive *)v4, (int)&a2);
      v54 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
              v98,
              a2);
      LOBYTE(v102) = 29;
      v55 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v54, v97, 18);
      v56 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v55, v80, v95);
      LOBYTE(v102) = 30;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 52, v56);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97);
      LOBYTE(v102) = 3;
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
      ReadString((CArchive *)v4, (int)&a2);
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 60, a2);
      ReadString((CArchive *)v4, (int)&a2);
      v57 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
              v98,
              a2);
      LOBYTE(v102) = 31;
      v58 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v57, v97, 40);
      v59 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v58, v81, v96);
      LOBYTE(v102) = 32;
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)this + 56, v59);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v97);
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v98);
    }
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&a2);
  }
LABEL_39:
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v101);
}

Судя по коду, читаются следующие данные врача-аналитика:

  • Имя

  • ID

  • Тип ID

  • Офис

  • Адрес

  • Уровень компетенции

  • Почтовый индекс

  • Город

  • Штат

Затем методом CarctPatient::Load читаются данные пациента.

CarctPatient::Load
void __thiscall CarctPatient::Load(CarctPatient *this, struct CarcDiskHeaderAbs *a2)
{
  CarctPatient *v2; // esi
  int v3; // edi
  int v4; // ecx
  unsigned int v5; // edx
  unsigned __int8 *v6; // eax
  unsigned __int8 *v7; // edx
  unsigned int v8; // eax
  unsigned int v9; // ecx
  WORD v10; // ax
  WORD v11; // si
  int v12; // edi
  int v13; // eax
  __int16 v14; // ax
  WORD v15; // ax
  int v16; // esi
  unsigned int v17; // eax
  bool v18; // cc
  int v19; // ecx
  unsigned __int16 v20; // cx
  WORD v21; // ax
  WORD v22; // di
  WORD v23; // ax
  WORD v24; // si
  int v25; // eax
  CarcDiskHeaderAbs *v26; // edx
  int v27; // ecx
  unsigned int v28; // edx
  unsigned __int16 *v29; // eax
  CarcDiskHeaderAbs *v30; // ecx
  CarcDiskHeaderAbs *v31; // edi
  int v32; // eax
  int v33; // eax
  int v34; // eax
  int v35; // eax
  int v36; // eax
  int v37; // eax
  int v38; // eax
  int v39; // eax
  int v40; // eax
  int v41; // eax
  int v42; // eax
  int v43; // eax
  const wchar_t *v44; // eax
  const wchar_t *v45; // eax
  int v46; // eax
  int v47; // eax
  const wchar_t *v48; // eax
  const wchar_t *v49; // eax
  const wchar_t *v50; // eax
  int v51; // [esp-8h] [ebp-B8h]
  int v52; // [esp-8h] [ebp-B8h]
  int v53; // [esp-8h] [ebp-B8h]
  const wchar_t *v54; // [esp-4h] [ebp-B4h]
  const wchar_t *v55; // [esp-4h] [ebp-B4h]
  const wchar_t *v56; // [esp-4h] [ebp-B4h]
  const wchar_t *v57; // [esp-4h] [ebp-B4h]
  const wchar_t *v58; // [esp-4h] [ebp-B4h]
  const wchar_t *v59; // [esp-4h] [ebp-B4h]
  const wchar_t *v60; // [esp-4h] [ebp-B4h]
  const wchar_t *v61; // [esp-4h] [ebp-B4h]
  const wchar_t *v62; // [esp-4h] [ebp-B4h]
  const wchar_t *v63; // [esp-4h] [ebp-B4h]
  const wchar_t *v64; // [esp-4h] [ebp-B4h]
  const wchar_t *v65; // [esp-4h] [ebp-B4h]
  int v66; // [esp-4h] [ebp-B4h]
  int v67; // [esp-4h] [ebp-B4h]
  int v68; // [esp-4h] [ebp-B4h]
  int v69; // [esp+Ch] [ebp-A4h]
  WORD v70; // [esp+10h] [ebp-A0h]
  WORD v71; // [esp+10h] [ebp-A0h]
  WORD v72; // [esp+14h] [ebp-9Ch]
  WORD v73; // [esp+14h] [ebp-9Ch]
  int v74; // [esp+18h] [ebp-98h] BYREF
  DOUBLE pvtime; // [esp+1Ch] [ebp-94h] BYREF
  int v76; // [esp+24h] [ebp-8Ch]
  int v77; // [esp+28h] [ebp-88h]
  int v78; // [esp+2Ch] [ebp-84h] BYREF
  int v79; // [esp+30h] [ebp-80h] BYREF
  char v80[4]; // [esp+34h] [ebp-7Ch] BYREF
  __time64_t Time; // [esp+38h] [ebp-78h] BYREF
  int v82; // [esp+40h] [ebp-70h] BYREF
  CarcDiskHeaderAbs *v83; // [esp+44h] [ebp-6Ch] BYREF
  struct tm v84; // [esp+48h] [ebp-68h]
  struct tm Tm; // [esp+6Ch] [ebp-44h] BYREF
  struct _SYSTEMTIME SystemTime; // [esp+90h] [ebp-20h] BYREF
  int v87; // [esp+ACh] [ebp-4h]

  v2 = this;
  v78 = (int)this;
  v83 = a2;
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v74);
  v87 = 0;
  v3 = *((_DWORD *)v83 + 1);
  v69 = v3;
  if ( *((_BYTE *)v83 + 8) != 48 )
  {
    v82 = 48;
    if ( CarcDiskHeaderAbs::IsNewerVersion(v83) )
    {
      v42 = sub_5C622FB0((_DWORD *)v3);
      v43 = (*(int (__thiscall **)(int, char *))(*(_DWORD *)v42 + 32))(v42, (char *)&Time + 4);
      LOBYTE(v87) = 1;
      v44 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v43);
      AfxThrowArchiveException(7, v44);
    }
    if ( CarcDiskHeaderAbs::GetExportVersion(v83) > 1 )
    {
      if ( (*(_BYTE *)(v3 + 24) & 1) == 0 )
      {
        v45 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20);
        AfxThrowArchiveException(4, v45);
      }
      v4 = *(_DWORD *)(v3 + 40);
      v5 = *(_DWORD *)(v3 + 44);
      if ( v4 + 1 > v5 )
        CArchive::FillBuffer((CArchive *)v3, v4 - v5 + 1);
      v6 = *(unsigned __int8 **)(v3 + 40);
      v7 = v6 + 1;
      v8 = *v6;
      *(_DWORD *)(v3 + 40) = v7;
      v82 = v8;
      if ( v8 > 0x35 )
      {
        v46 = sub_5C622FB0((_DWORD *)v3);
        v47 = (*(int (__thiscall **)(int, char *))(*(_DWORD *)v46 + 32))(v46, (char *)&Time + 4);
        LOBYTE(v87) = 2;
        v48 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v47);
        AfxThrowArchiveException(7, v48);
      }
      if ( v8 - 50 <= 2 )
      {
        if ( (*(_BYTE *)(v3 + 24) & 1) == 0 )
        {
          v49 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20);
          AfxThrowArchiveException(4, v49);
        }
        v9 = *(_DWORD *)(v3 + 44);
        if ( (unsigned int)(v7 + 4) > v9 )
          CArchive::FillBuffer((CArchive *)v3, (unsigned int)&v7[-v9 + 4]);
        *(_DWORD *)(v3 + 40) += 4;
      }
    }
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
      (char *)v2 + 8,
      *((_DWORD *)v83 + 6));
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(
      (char *)v2 + 12,
      *((_DWORD *)v83 + 7));
    if ( (unsigned int)v82 < 0x34 )
    {
      sub_5C6210F0((CArchive *)v3, (int)&v83);
      if ( (_WORD)v83 != 32484 )
      {
        if ( v82 == 49 )
        {
          Time = time64(0);
          pvtime = 0.0;
          v76 = 0;
          if ( localtime64_s(&Tm, &Time) )
            goto LABEL_23;
          v84 = Tm;
          SystemTime.wYear = LOWORD(Tm.tm_year) + 1900;
          SystemTime.wMonth = LOWORD(Tm.tm_mon) + 1;
          SystemTime.wDayOfWeek = Tm.tm_wday;
          SystemTime.wDay = Tm.tm_mday;
          SystemTime.wHour = Tm.tm_hour;
          SystemTime.wMinute = Tm.tm_min;
          SystemTime.wSecond = Tm.tm_sec;
          SystemTime.wMilliseconds = 0;
          v13 = sub_5C622D60(&pvtime, &SystemTime);
          v76 = 0;
          if ( !v13 )
LABEL_23:
            v76 = 1;
          SystemTime = 0i64;
          v14 = sub_5C642CC0(&pvtime);
          v15 = v14 - (_WORD)v83;
        }
        else
        {
          v15 = (_WORD)v83 + 1900;
          SystemTime = 0i64;
        }
        SystemTime.wYear = v15;
        SystemTime.wMonth = 1;
        *(_DWORD *)&SystemTime.wDay = 1;
        *(_DWORD *)&SystemTime.wMinute = 0;
        *((_DWORD *)v2 + 7) = sub_5C622D60((DOUBLE *)((char *)v2 + 20), &SystemTime) == 0;
      }
    }
    else
    {
      ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v83);
      LOBYTE(v87) = 3;
      if ( (unsigned int)v82 >= 0x35 )
        ReadString((CArchive *)v3, (int)v2 + 16);
      ReadString((CArchive *)v3, (int)&v83); // Дата рождения пациента
      if ( *((_DWORD *)v83 - 3) == 14 )
      {
        v54 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(
                                   &v83,
                                   v80,
                                   4);
        LOBYTE(v87) = 4;
        v72 = wtol(v54);
        LOBYTE(v87) = 3;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v55 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                                   &v83,
                                   v80,
                                   4,
                                   2);
        LOBYTE(v87) = 5;
        v70 = wtol(v55);
        LOBYTE(v87) = 3;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v56 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                                   &v83,
                                   v80,
                                   6,
                                   2);
        LOBYTE(v87) = 6;
        v77 = wtol(v56);
        LOBYTE(v87) = 3;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v57 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                                   &v83,
                                   v80,
                                   8,
                                   2);
        LOBYTE(v87) = 7;
        v79 = wtol(v57);
        LOBYTE(v87) = 3;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v58 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                                   &v83,
                                   v80,
                                   10,
                                   2);
        LOBYTE(v87) = 8;
        HIDWORD(Time) = wtol(v58);
        LOBYTE(v87) = 3;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v59 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Right(
                                   &v83,
                                   v80,
                                   2);
        LOBYTE(v87) = 9;
        v10 = wtol(v59);
        LOBYTE(v87) = 3;
        v11 = v10;
        CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)v80);
        v12 = v78;
        SystemTime = 0i64;
        SystemTime.wYear = v72;
        SystemTime.wMonth = v70;
        SystemTime.wDay = v77;
        SystemTime.wHour = v79;
        SystemTime.wMinute = WORD2(Time);
        SystemTime.wSecond = v11;
        *(_DWORD *)(v12 + 28) = sub_5C622D60((DOUBLE *)(v78 + 20), &SystemTime) == 0;
        v3 = v69;
      }
      LOBYTE(v87) = 0;
      CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v83);
    }
    v16 = v78;
    if ( *(_DWORD *)(v78 + 28) == 1 )
      *(_DWORD *)(v78 + 28) = 2;
    ReadString((CArchive *)v3, v16 + 36); // Адрес пациента
    ReadString((CArchive *)v3, v16 + 40); // Жалоба пациента
    ReadString((CArchive *)v3, v16 + 52); // Почтовый индекс пациента
    ReadString((CArchive *)v3, v16 + 44); // Город пациента
    ReadString((CArchive *)v3, v16 + 48); // Штат пациента
    if ( (unsigned int)v82 < 0x34 )
    {
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 56, &word_5C659A40);
      ReadString((CArchive *)v3, v16 + 60);
    }
    else
    {
      ReadString((CArchive *)v3, v16 + 56); // Страна пациента
      ReadString((CArchive *)v3, v16 + 60); // Телефон пациента
      v17 = v82;
      if ( (unsigned int)v82 > 0x34 )
        goto LABEL_34;
    }
    ReadString((CArchive *)v3, (int)&v74);
    v17 = v82;
LABEL_34:
    if ( v17 < 0x33 )
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 64, &word_5C659A40);
    else
      ReadString((CArchive *)v3, v16 + 64); // Мобильный телефон пациента
    ReadString((CArchive *)v3, v16 + 32); // Пол пациента
    ReadString((CArchive *)v3, v16 + 76); // Рост пациента
    ReadString((CArchive *)v3, v16 + 72); // Вес пациента
    sub_5C6210F0((CArchive *)v3, (int)&v78);
    v18 = (unsigned int)v82 <= 0x30;
    *(_DWORD *)(v16 + 80) = (unsigned __int16)v78;
    if ( v18 )
    {
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v16 + 68, &word_5C659A40);
      v19 = v16 + 84;
    }
    else
    {
      ReadString((CArchive *)v3, v16 + 68);
      v19 = v16 + 84;
      if ( (unsigned int)v82 >= 0x34 )
      {
        ReadString((CArchive *)v3, v16 + 84);
        goto LABEL_64;
      }
    }
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(v19, &word_5C659A40);
    goto LABEL_64;
  }
  ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(&v82);
  LOBYTE(v87) = 10;
  pvtime = 0.0;
  v76 = 2;
  v20 = *((_WORD *)v83 + 4);
  if ( v20 > 0x3530u )
  {
    if ( v20 >= 0x3B30u )
      ReadString((CArchive *)v3, (int)v2 + 16);
    ReadString((CArchive *)v3, (int)&v82);
    if ( *(_DWORD *)(v82 - 12) != 14 )
      goto LABEL_50;
    v60 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(
                               &v82,
                               (char *)&Time + 4,
                               4);
    LOBYTE(v87) = 11;
    v79 = wtol(v60);
    LOBYTE(v87) = 10;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    v61 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                               &v82,
                               (char *)&Time + 4,
                               4,
                               2);
    LOBYTE(v87) = 12;
    v77 = wtol(v61);
    LOBYTE(v87) = 10;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    v62 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                               &v82,
                               (char *)&Time + 4,
                               6,
                               2);
    LOBYTE(v87) = 13;
    v71 = wtol(v62);
    LOBYTE(v87) = 10;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    v63 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                               &v82,
                               (char *)&Time + 4,
                               8,
                               2);
    LOBYTE(v87) = 14;
    v73 = wtol(v63);
    LOBYTE(v87) = 10;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    v64 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Mid(
                               &v82,
                               (char *)&Time + 4,
                               10,
                               2);
    LOBYTE(v87) = 15;
    v21 = wtol(v64);
    LOBYTE(v87) = 10;
    v22 = v21;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    v65 = *(const wchar_t **)ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Right(
                               &v82,
                               (char *)&Time + 4,
                               2);
    LOBYTE(v87) = 16;
    v23 = wtol(v65);
    LOBYTE(v87) = 10;
    v24 = v23;
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    SystemTime = 0i64;
    SystemTime.wYear = v79;
    SystemTime.wMonth = v77;
    SystemTime.wDay = v71;
    SystemTime.wHour = v73;
    SystemTime.wMinute = v22;
    SystemTime.wSecond = v24;
    v25 = sub_5C622D60(&pvtime, &SystemTime);
    v3 = v69;
    v2 = (CarctPatient *)v78;
    goto LABEL_49;
  }
  sub_5C6210F0((CArchive *)v3, (int)&v78);
  if ( (_WORD)v78 != 32484 )
  {
    SystemTime = 0i64;
    SystemTime.wYear = v78 + 1900;
    SystemTime.wMonth = 1;
    *(_DWORD *)&SystemTime.wDay = 1;
    *(_DWORD *)&SystemTime.wMinute = 0;
    v25 = sub_5C622D60(&pvtime, &SystemTime);
LABEL_49:
    v76 = v25 == 0;
  }
LABEL_50:
  ReadString((CArchive *)v3, (int)v2 + 36);
  ReadString((CArchive *)v3, (int)v2 + 40);
  ReadString((CArchive *)v3, (int)v2 + 52);
  ReadString((CArchive *)v3, (int)v2 + 44);
  ReadString((CArchive *)v3, (int)v2 + 48);
  if ( *((_WORD *)v83 + 4) > 0x3530u )
    ReadString((CArchive *)v3, (int)v2 + 56);
  ReadString((CArchive *)v3, (int)v2 + 60);
  v26 = v83;
  if ( *((_WORD *)v83 + 4) < 0x3B30u )
  {
    ReadString((CArchive *)v3, (int)&v74);
    v26 = v83;
  }
  if ( *((_WORD *)v26 + 4) > 0x3530u )
    ReadString((CArchive *)v3, (int)v2 + 64);
  ReadString((CArchive *)v3, (int)v2 + 32);
  ReadString((CArchive *)v3, (int)v2 + 76);
  ReadString((CArchive *)v3, (int)v2 + 72);
  if ( (*(_BYTE *)(v3 + 24) & 1) == 0 )
  {
    v50 = (const wchar_t *)_CIP<IBindStatusCallback,&_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback *(v3 + 20);
    AfxThrowArchiveException(4, v50);
  }
  v27 = *(_DWORD *)(v3 + 40);
  v28 = *(_DWORD *)(v3 + 44);
  if ( v27 + 2 > v28 )
    CArchive::FillBuffer((CArchive *)v3, v27 - v28 + 2);
  v29 = *(unsigned __int16 **)(v3 + 40);
  v77 = *v29;
  v30 = v83;
  *(_DWORD *)(v3 + 40) = v29 + 1;
  if ( *((_WORD *)v30 + 4) > 0x3530u )
  {
    ReadString((CArchive *)v3, (int)v2 + 68);
    ReadString((CArchive *)v3, (int)v2 + 84);
  }
  v31 = v83;
  v32 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
          &v79,
          *((_DWORD *)v83 + 6));
  LOBYTE(v87) = 17;
  v33 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v32, (char *)&Time + 4, 50);
  v34 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v33, v51, v66);
  LOBYTE(v87) = 18;
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 8, v34);
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
  LOBYTE(v87) = 10;
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79);
  v35 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
          &v79,
          *((_DWORD *)v31 + 7));
  LOBYTE(v87) = 19;
  v36 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v35, (char *)&Time + 4, 50);
  v37 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v36, v52, v67);
  LOBYTE(v87) = 20;
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 12, v37);
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
  LOBYTE(v87) = 10;
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79);
  v38 = v76;
  *(DOUBLE *)((char *)v2 + 20) = pvtime;
  *((_DWORD *)v2 + 7) = v38;
  *((_DWORD *)v2 + 20) = (_WORD)v77 != 0;
  if ( !*(_DWORD *)(*((_DWORD *)v2 + 21) - 12) )
  {
    v39 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
            &v79,
            *((_DWORD *)v2 + 21));
    LOBYTE(v87) = 21;
    v40 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Trim(v39, (char *)&Time + 4, 50);
    v41 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(v40, v53, v68);
    LOBYTE(v87) = 22;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=((char *)v2 + 84, v41);
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)((char *)&Time + 4));
    CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v79);
  }
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v82);
LABEL_64:
  CMFCRibbonInfo::XID::~XID((CMFCRibbonInfo::XID *)&v74);
}

Метод читает:

  • Дату рождения пациента

  • Адрес

  • Жалобу

  • Почтовый индекс

  • Город

  • Штат

В зависимости от версии также читаются:

  • Страна

  • Телефон

  • Мобильный телефон

  • Пол

  • Рост

  • Вес

Поскольку статья и так получилась перегруженной деталями, я не буду утомлять вас дальнейшим разбором реверс-инжиниринга. Перейдем сразу к итогу.

В результате получился вот такой код на Python, который читает XCM-файл и строит графики ЭКГ:

XCM_Reader
import bz2
import struct
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Button
import sys
from datetime import datetime, timedelta
import base64
import zlib

current_index = 0
sample_rate = 200

def find(haystack, start_offset, needle):
    if start_offset >= len(haystack):
        return -1
    if not needle:
        return start_offset
    pos = haystack.find(needle, start_offset)
    return pos if pos != -1 else -1

class Reader:
    cur_pos = 0
    raw_ecg = bytearray()

    def __init__(self, buf):
        self.buf = buf
    
    def seek(self, offset):
        self.cur_pos += offset

    def read_byte(self):
       value = self.buf[self.cur_pos]
       self.cur_pos += 1
       return value
    
    def read_short(self):
       value = struct.unpack('<H', self.buf[self.cur_pos:self.cur_pos + 2])[0]
       self.cur_pos += 2
       return value
    
    def read_int(self):
       value = struct.unpack('<I', self.buf[self.cur_pos:self.cur_pos + 4])[0]
       self.cur_pos += 4
       return value
    
    def read_long(self):
       value = struct.unpack('<Q', self.buf[self.cur_pos:self.cur_pos + 8])[0]
       self.cur_pos += 8
       return value
    
    def read_string_length(self):
        char_size = 1
        bLength = self.read_byte()
        if bLength < 0xff:
            return (char_size, bLength)
            
        wLength = self.read_short()
        if  wLength == 0xfffe:
            char_size = 2
            bLength = self.read_byte()
            if bLength < 0xff:
                return (char_size, bLength)
            wLength = self.read_short()
        
        if wLength < 0xffff:
            return (char_size, wLength)
        
        dwLength = self.read_int()
        if dwLength < 0xffffffff:
            return (char_size, dwLength)
        
        qwLength = self.read_long()
        return (char_size, qwLength)
    
    def read_string(self): 
        string_length = self.read_string_length()
        char_size = string_length[0]
        len = string_length[1] * char_size
        s = self.buf[self.cur_pos:self.cur_pos + len]
        self.cur_pos += len

        if char_size == 1:
            return s.decode("windows-1252")

        if char_size == 2:
            return s.decode("utf-16")
        
    def decompress(self, data):
        try:
            return bz2.decompress(data)
        except Exception:
            return data

    def decompress_data(self):
        bz_size = self.read_int()
        raw_data = bytearray()
        while True:
            compressed_size = self.read_int()
            decompressed_data = self.decompress(self.buf[self.cur_pos:self.cur_pos + compressed_size])
            raw_data.extend(decompressed_data)
            self.cur_pos += compressed_size
            bz_size = self.read_int()
            if bz_size == 0:
                break
        return raw_data
    
    def read_block(self):
        print(self.read_string())
        print("Size: {}".format(self.read_int()))
        block_size = self.read_int()
        print("Block size: {}".format(block_size))
        block_type = self.read_byte()
        if block_type > 51:
           raise ValueError('Incorrect xcm!')
        if (block_type == 51) and (block_size <= 265365):
            return self.decompress_data()
        return bytearray()
    
class DiaryReader(Reader):
    BASE_DATE = datetime(1899, 12, 30)

    def read_count(self):
       count = self.read_short()
       if count != 0xFFFF:
           return count
       return self.read_int()
    
    def read_double(self):
        value = struct.unpack('<d', self.buf[self.cur_pos:self.cur_pos + 8])[0]
        self.cur_pos += 8
        return value
    
    def read_datetime(self):
        try:
            time = self.read_double()
            days = int(time)
            fractional_seconds = (time - days) * 86400
            return self.BASE_DATE + timedelta(days=days, seconds=fractional_seconds)
        except:
            return self.BASE_DATE

    def read_diary(self):
        print("Diary:")
        self.seek(2)
        print("ID: {}".format(self.read_string()))
        print("Exam number: {}".format(self.read_string()))
        print("Patient Name: {}".format(self.read_string()))
        self.seek(12)
        count = self.read_count()
        print("Activites count: {}".format(count))
        self.seek(4)
        start_dt = self.read_datetime()
        print("Exam Start Date: " + start_dt.strftime("%Y-%m-%d %H:%M:%S"))
        self.seek(12)
        end_dt = self.BASE_DATE
        for i in range(count):
            if (start_dt != self.BASE_DATE) and (end_dt == self.BASE_DATE):
                print("Activity {}: {} {}".format(i + 1, self.read_string(), start_dt.strftime("%H:%M")))
            elif (start_dt != self.BASE_DATE) and (end_dt != self.BASE_DATE):
                print("Activity {}: {} {} - {}".format(i + 1, self.read_string(), start_dt.strftime("%H:%M"), end_dt.strftime("%H:%M")))
            else:
                print("Activity {}: {}".format(i + 1, self.read_string()))
            self.seek(12)
            print("Symptom: {}".format(self.read_string()))
            self.seek(4)
            start_dt = self.read_datetime()
            self.seek(4)
            end_dt = self.read_datetime()

class XCMReader(Reader):
    meta_data = list()
    attachment_data = list()
    
    def read_date(self):
        year = self.read_short()
        month = self.read_short()
        day = self.read_short()
        hour = self.read_short()
        minute = self.read_short()
        second = self.read_short()
        dt = datetime(year, month, day, hour, minute, second)
        print("Date: " + dt.strftime("%Y-%m-%d %H:%M:%S"))

    def get_export_version(self):
        if self.xcm_type > 49:
            return 0x3FFF
        if self.xcm_type != 49:
            return (self.xcm_type == 48) - 1
        if self.xcm_major_version > 55:
            return 0x3FFF
        match self.xcm_major_version:
            case 55: 
                if self.xcm_minor_version <= 48:
                    if self.xcm_minor_version == 48:
                        return 7
                    else:
                        return -1
                return 0x3FFF
            case 54:
                if self.xcm_minor_version == 49:
                    return 6
                else:
                    return 8 * (self.xcm_minor_version == 50) - 1
            case 53:
                if self.xcm_minor_version == 48:
                    return 5
                else:
                    if self.xcm_minor_version == 49:
                        return 6
                return -1
            case 52:
                return 4
            case 51:
                return 3
            case 50:
                return 2
            case _:
                return 2 * (self.xcm_major_version == 49) - 1
            
    def is_newer_version(self):
        if self.xcm_type <= 49:
            if self.xcm_type != 49:
                return False
            if self.xcm_major_version <= 55:
                if self.xcm_major_version == 55:
                    return self.xcm_minor_version > 48
                return False
        return True
        
    def read_holter_header(self):
        if self.read_byte() != 26:
            return
        print("Holter:")
        print("Exam number: " + self.read_string())
        print("ID: " + self.read_string())
        print("Analyst: " + self.read_string())
        print("Patient Name: " + self.read_string())
        print("Patient Last Name: " + self.read_string())
        self.read_date()
        print("Requester: " + self.read_string())
        print("Office: " + self.read_string())
        print("Holter: " + self.read_string())
        self.seek(6)
        if self.get_export_version() >= 3: 
            print("Check sum: " + self.read_string())
            self.seek(2)
            if self.get_export_version() >= 4:
                print("Health plan: " + self.read_string())
                if self.get_export_version() >= 5:
                    print(self.read_string())
                    base64_str = self.read_string()
                    json_str = zlib.decompress(base64.b64decode(base64_str)).decode()
                    print("Holter JSON: " + json_str)

    def read_analyst(self):
         if self.xcm_type != 48:
             if not self.is_newer_version() and self.get_export_version() > 1:
                analyst_type = self.read_byte()
                if analyst_type > 53:
                    raise ValueError('Incorrect xcm!')
                if analyst_type - 50 <= 2:
                    self.seek(4)
                print("Analyst:")
                print("Analyst name: " + self.read_string())
                print("Analyst id: " + self.read_string())
                print("Analyst type of id: " + self.read_string())
                print("Analyst office: " + self.read_string())
                print("Analyst addres: " + self.read_string())
                print("Analyst compL:" + self.read_string())
                print("Analyst ZIP code: " + self.read_string())
                print("Analyst city: " + self.read_string())
                print("Analyst state: " + self.read_string())

                if analyst_type < 52:
                    print(self.read_string())
                else:
                    print("Analyst contry: " + self.read_string())
                    print(self.read_string())

                if analyst_type < 53:
                    print(self.read_string())

                if analyst_type < 51:
                    print(self.read_string())
                    if analyst_type >= 49:
                        print(self.read_string())
                else:
                    print(self.read_string())
                    print(self.read_string())
                    print(self.read_string())

    def read_patient(self):
        if self.xcm_type != 48:
             if not self.is_newer_version() and self.get_export_version() > 1:
                patient_type = self.read_byte()
                if patient_type > 53:
                   raise ValueError('Incorrect xcm!')
                if patient_type - 50 <= 2:
                    self.seek(4)
                if patient_type < 52:
                    raise ValueError('Not supported!')
                else:
                    print("Patient:")
                    if patient_type >= 53:
                        print(self.read_string())
                    date = datetime.strptime(self.read_string(), "%Y%m%d%H%M%S").strftime("%Y-%m-%d %H:%M:%S")
                    print("Patient date of birth: " + date)

                print("Patient addres: " + self.read_string())
                print("Patient compL:" + self.read_string())
                print("Patient ZIP code: " + self.read_string())
                print("Patient city: " + self.read_string())
                print("Patient state: " + self.read_string())

                if patient_type < 52:
                    print(self.read_string())
                else:
                    print("Patient contry: " + self.read_string())
                    print("Patient phone: " + self.read_string())

                if patient_type < 53:
                    print(self.read_string())

                if patient_type > 51:
                    print("Patient mobile phone: " + self.read_string())

                print("Patient sex: " + self.read_string())
                print("Patient height: " + self.read_string())
                print("Patient weight: " + self.read_string())
                if patient_type < 53:
                    self.seek(2)
                if patient_type > 48:
                    print(self.read_string())
                    if patient_type <= 52:
                        print("Patient health plan: " + self.read_string())
                if patient_type == 53:
                    print(self.read_string())
                    print(self.read_string())
                    print("Patient health plan: " + self.read_string())
                self.seek(6)               

    def read_doctor(self):
        if self.xcm_type != 48:
            if not self.is_newer_version() and self.get_export_version() > 1:
                doctor_type = self.read_byte()
                if doctor_type == 255:
                    self.seek(3)
                    doctor_type = self.read_byte()
                if doctor_type > 53:
                   raise ValueError('Incorrect xcm!')
                if doctor_type - 50 <= 2:
                    self.seek(4)
                print("Doctor:")
                print(self.read_string())
                print("Doctor name: " + self.read_string())
                print("Doctor office: " + self.read_string())
                print(self.read_string())
                print(self.read_string())
                print(self.read_string())
                print(self.read_string())
                print(self.read_string())

                if doctor_type < 52:
                    print(self.read_string())
                    print(self.read_string())
                
                if doctor_type == 53:
                    print(self.read_string())
                    print(self.read_string())

                if doctor_type < 53:
                    print(self.read_string())

                if doctor_type < 51:
                    print(self.read_string())
                    if doctor_type < 49:
                        return
                else:
                    print(self.read_string())
                    print(self.read_string())
                print(self.read_string())
                if doctor_type == 52:
                    print(self.read_string())
                    self.seek(4)
                if doctor_type <= 52:
                    self.seek(12)

    def read_device_config(self):
        if self.xcm_type != 48:
            if not self.is_newer_version() and self.get_export_version() > 1:
               device_config_type = self.read_byte()
               if device_config_type > 49:
                   raise ValueError('Incorrect xcm!')
               self.seek(4)
               print("Device config:")
               print(self.read_string())
               print(self.read_string())
               print(self.read_string())

    def read_channel(self):
        if self.xcm_type != 48:
            if not self.is_newer_version() and self.get_export_version() > 1:
                channel_type = self.read_byte()
                if channel_type > 49:
                   raise ValueError('Incorrect xcm!')
                print("Channel:")
                print(self.read_short())
                print(self.read_short())
                print(self.read_short())

    def read_channels(self, channels):
        print("Channeles: {}".format(channels))
        for _ in range(channels):
            self.read_channel()

    def read_signal(self):
        if self.xcm_type != 48:
            if not self.is_newer_version() and self.get_export_version() > 1:
                print("Signal:")
                self.seek(1)
                signal_type = self.read_byte()
                if signal_type > 53:
                   raise ValueError('Incorrect xcm!')
                print("ADC bits: {}".format(self.read_short()))
                if signal_type > 50:
                    print("Format ID: {}".format(self.read_short()))
                print(self.read_int())
                channels = self.read_short()
                print(channels)
                print(self.read_string())
                print(self.read_int())
                print("Number of samples: {}".format(self.read_int()))
                print("Sample rate: {}".format(self.read_short()))
                self.read_date()
                print(self.read_short())
                print(self.read_short())
                if (self.xcm_major_version > 52) and (self.xcm_major_version < 54) and (self.xcm_major_version != 55):
                    self.seek(8)
                elif self.xcm_major_version == 55:
                    self.seek(18)
                self.read_channels(channels)

    def read_exam(self):
        self.cur_pos = find(self.buf, self.cur_pos, bytearray(b'\xff\xfe\xff'))
        print("Exam:")
        self.raw_ecg = self.read_block()

    def read_meta_data(self):
        def is_need_read():
            result = self.read_byte()
            if result == 0:
                self.seek(1)
                result = self.read_byte()
            if result > 1:
                self.seek(-2)
                return self.read_byte()
            if result == 0xFE:
                self.seek(-3)
                return self.read_byte()
            return result
        print("Data:")
        while is_need_read():
            self.meta_data.append(self.read_block())
        if self.read_byte() == 255:
            self.seek(-1)
            self.meta_data.append(self.read_block())
        else:
            self.seek(-1)

    def read_attachments(self):
        print("Attachments:")
        type = self.read_byte()
        if type > 51:
           raise ValueError('Incorrect xcm!')
        self.read_date()
        self.read_date()
        self.seek(3)
        print(self.read_string())
        print(self.read_string())
        print(self.read_string())
        print(self.read_string())

        if (self.xcm_major_version == 55) or (self.xcm_major_version == 52):
            self.attachment_data.append(self.read_block())

        if self.read_string() == '{^¨ATTACHS~`}':
            val = self.read_short()
            for _ in range(val):
                attachment_name = self.read_string()
                print("Attachment name: {}".format(attachment_name))
                self.seek(4)
                block = self.read_block()
                if attachment_name.find(".ped") != -1:
                    diary_reader = DiaryReader(block)
                    diary_reader.read_diary()
                self.attachment_data.append(block)

    def read_xcm(self):
        self.xcm_type = self.read_byte()
        self.xcm_major_version = self.read_byte()
        self.xcm_minor_version = self.read_byte()
        if self.xcm_type == 49:
            self.read_holter_header()
            self.read_analyst()
            self.read_patient()
            self.read_doctor()
            self.read_device_config()
            self.read_signal()
            self.read_exam()
            self.read_meta_data()
            self.read_attachments()
    
    def save_meta_data(self):
        with open('ecg.bin', "wb") as ecg_file:
            ecg_file.write(self.raw_ecg)

        meta_ind = 0
        for data in self.meta_data:
            with open('meta_data_{}.bin'.format(meta_ind), "wb") as meta_data_file:
                meta_data_file.write(data)
                meta_ind += 1

        attachment_ind = 0
        for data in self.attachment_data:
            with open('attachment_{}.bin'.format(attachment_ind), "wb") as attachment_file:
                attachment_file.write(data)
                attachment_ind += 1

def show_ecg(raw_ecg, adc_range = 5, adc_zero = 128, initial_val = 256):
    raw_ecg = np.frombuffer(raw_ecg, dtype=np.uint8)
    channels = 3
    labels = ["I", "II", "III"]
    ch_start_offset = 262144
    num_samples = len(raw_ecg) - (ch_start_offset * channels)
    x = np.zeros((channels, num_samples), dtype=np.float16)
    
    for ch in range(channels):
        start = ch * ch_start_offset
        x[ch] = (raw_ecg[start : start + num_samples].astype(np.float16) - adc_zero) * (adc_range / initial_val)

    sample_len = x.shape[1]
    display_range = 2000
    current_index = 0

    fig, ax = plt.subplots(figsize=(20, 10), nrows=channels, ncols=1)
    fig.subplots_adjust(hspace=0.15, left=0.03, right=0.98, top=0.95, bottom=0.1)
    fig.suptitle("Channels")
    plt.xlabel("Time (s)")

    lines = []
    tick_positions = np.linspace(current_index, current_index + display_range, 10)
    tick_labels = np.arange(current_index // sample_rate, (current_index + display_range) // sample_rate)
    
    for i, a in enumerate(ax):
        line, = a.plot(range(current_index, (current_index + display_range)), x[i][current_index:current_index + display_range])
        a.set_ylabel(labels[i], rotation=0)
        a.set_xticks(tick_positions)
        a.set_xticklabels(tick_labels)
        a.grid(True, alpha=0.2)
        lines.append(line)

    def update_plot(forward=True):
        nonlocal current_index
        step = display_range if forward else -display_range
        current_index += step
        
        if current_index < 0:
            current_index = 0
        elif current_index + display_range > sample_len:
            current_index = sample_len - display_range
        
        tick_labels = np.arange(current_index // sample_rate, (current_index + display_range) // sample_rate)

        for i, line in enumerate(lines):
            line.set_ydata(x[i][current_index:current_index + display_range])
            line.axes.set_xticklabels(tick_labels)
        plt.draw()

    ax_next_button = plt.axes([0.81, 0.01, 0.1, 0.05])
    ax_prev_button = plt.axes([0.7, 0.01, 0.1, 0.05])
    
    next_button = Button(ax_next_button, 'Next')
    prev_button = Button(ax_prev_button, 'Previous')
    
    next_button.on_clicked(lambda event: update_plot(forward=True))
    prev_button.on_clicked(lambda event: update_plot(forward=False))

    plt.show()

def main():
    if len(sys.argv) != 2:
        print("Usage: python XCM_Reader.py <xcm file name>")
        return
    
    filename = sys.argv[1]
    
    try:
        with open(filename, "rb") as file:
            buf = file.read()
            reader = XCMReader(buf)
            reader.read_xcm()
            reader.save_meta_data()
            show_ecg(reader.raw_ecg)
    except FileNotFoundError:
        print("File not found.")
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    main()

Пояснение к коду

Класс Reader

Это базовый класс для парсинга бинарных данных. Он представляет собой реализацию логики класса CArchive из библиотеки MFC на Python. В коде используется специфическая структура чтения строк и длин, характерная для сериализации объектов в Visual C++. Данные внутри XCM-файла упакованы блоками с помощью bz2, которые метод decompress_data собирает в единый массив байтов. Это позволяет извлекать как сигналы ЭКГ, так и другие вложенные метаданные для последующей обработки.

Класс DiaryReader

Предназначен для парсинга дневника пациента: событий, зафиксированных во время холтеровского мониторирования (жалоб, активности). Время в файле хранится в формате double. Это соответствует стандарту COleDateTime в MFC, где целая часть — количество дней, прошедших с полуночи 30 декабря 1899 года, а дробная — часть суток.

Класс XCMReader

Непосредственно сам парсер. Формат XCM чувствителен к версиям, поэтому логика чтения постоянно ветвится. При чтении метаданных в методе read_holter_header извлекается основная информация о записи: номер обследования, ID, ФИО врача-аналитика и пациента. Работа начинается с проверки байта 26 (0x1A) — контрольного маркера начала заголовка. Дальнейшее ветвление напрямую зависит от версии экспорта: если она выше 3-й, читается контрольная сумма, а начиная с 4-й — добавляется информация о страховом плане. В 5-й версии разработчики внедрили «матрешку», где читается строка Base64, которая после декодирования и декомпрессии через zlib превращается в JSON с полным набором данных по холтеру.
При чтении данных врача-аналитика в методе read_analyst выполняется разбор полей, структура которых менялась от версии к версии. Номера типов (байт analyst_type) в диапазоне 50–53 диктуют, нужно ли делать смещение seek(4) и какие поля появятся в потоке дальше. В коде встречается много вызовов read_string(), результат которых просто выводится в консоль без присвоения переменным. Это неидентифицированные поля, которые в имеющихся файлах оказались пустыми, поэтому их назначение определить невозможно. Все методы до read_exam работают аналогично read_doctor — в них сохраняется та же логика с номерами типов, ветвлением по версиям и неидентифицированными полями.
В методе read_exam для поиска сигнатуры используется небольшая хитрость. В некоторых файлах между метаданными и сигналом встречаются вставки, из-за которых статический указатель промахивается мимо начала данных. Чтобы обойти проблему, я добавил поиск маркера \xff\xfe\xff — это гарантирует точное позиционирование на начало блока ЭКГ. После успешного нахождения сигнатуры вызывается read_block(), который вытягивает сырой сигнал для последующей декомпрессии.

Декомпрессия и отрисовка графиков (show_ecg)

Это финальный этап, где сырые данные превращаются в понятный вид. Внутри XCM-файла данные ЭКГ упакованы блоками, и метод decompress_data собирает их в единый массив байтов, выполняя распаковку bz2 (в старых версиях блоки могут быть не сжаты, тогда они читаются как есть).
Полученные данные из АЦП, которые обычно представляют собой значения от 0 до 255, необходимо центрировать и масштабировать. Для этого используется формула: (байт - adc_zero) * (диапазон / начальное значение), что превращает безликие цифры в реальное напряжение в микровольтах.
Важный нюанс заключается в разделении по каналам: в этом формате данные не перемешаны посемплово, а идут огромными кусками друг за другом. Код просто нарезает общий массив на три равные части (отведения I, II и III), используя фиксированное смещение ch_start_offset. Чтобы с результатом было удобно работать, я реализовал интерактивный интерфейс: графики можно листать кнопками «Next» и «Previous». Это позволяет комфортно просматривать многочасовую запись холтера, не теряя при этом нормальный масштаб.

Итог

Этот скрипт — результат объединения всех выводов, полученных при анализе ассемблерного и декомпилированного кода. Он умеет автоматически определять версии, распаковывать bz2-блоки и корректно интерпретировать специфические форматы дат. В результате проделанной работы появился готовый инструмент на Python, позволяющий читать и анализировать XCM-файлы из программы холтер-мониторинга.