// Platform.cpp: файл реализации
//
#include "pch.h"
#include "BK.h"
#include "Platform.h"

#include "BKMessageBox.h"
#include "ExceptionHalt.h"

#include "BK0010.h"
#include "BK001001.h"
#include "BK0011.h"
#include "BK0011M.h"
#include "BKMSTD10.h"
#include "BKMSTD10_EXT32.h"
#include "BKMSTD11.h"
#include "BKMSTD11M.h"
#include "FDDStd253.h"
#include "FDDStd326.h"
#include "FDDStd326Mem.h"
#include "FDD_A16M.h"
#include "FDD_SMK512.h"
#include "FDD_Samara.h"

#include "IRPS.h"
#include "AZBK.h"
#include "FDDOptok.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

// CPlatform
CPlatform g_Platform;

CPlatform::CPlatform()
	: m_nPlatformStatus(0)
	, m_nFoundFlags(0)
	, m_pMainFrame(nullptr)
	, m_pScript(nullptr)
	, m_pCPU(nullptr)
	, m_pDebugger(nullptr)
	, m_pScreen(nullptr)
	, m_pSound(nullptr)
	, m_pSpeaker(nullptr)
	, m_pCovox(nullptr)
	, m_pMenestrel(nullptr)
	, m_pAYSnd(nullptr)
	, m_pTape(nullptr)
	, m_nGotoAddress(BREAK_ADDRESS::ADDRESS_NONE)
	, m_nLowBound(1)
	, m_nHighBound(100000000)
	, m_nFDDCatchAddr(-1)
	, m_nFDDExitCatchAddr(-1)
	, m_bKillTimerEvent(false)
	, m_nCPUFreq_prev(-1)
{
	m_pScript    = std::make_unique<CScriptRunner>();
	m_pCPU       = std::make_unique<CCPU>();
	m_pDebugger  = std::make_unique<CDebugger>();
	m_pSound     = std::make_unique<CBkSound>();
	m_pSpeaker   = std::make_unique<CSpeaker>();
	m_pCovox     = std::make_unique<CCovox>();
	m_pMenestrel = std::make_unique<CMenestrel>();
	m_pAYSnd     = std::make_unique<CAYSnd>();
	m_pTape      = std::make_unique<CTape>();

	if (!(m_pScript && m_pCPU && m_pDebugger && m_pSound && m_pSpeaker && m_pCovox && m_pMenestrel && m_pAYSnd && m_pTape))
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		throw; // всё ломаем. и отменяем
	}

	ClearDevicesVector();
	m_nCPUTicks = 0;
	m_sOSCV.clear();
	m_fMemory_Mod = 0.0;
	m_fMemoryTicks = 0.0;
	m_sMTV.clear();
	m_fCpuTickTime = 0.0;
	m_fMedia_Mod = 0.0;
	m_fMediaTicks = 0.0;
	m_fFDD_Mod = 0.0;
	m_fFDDTicks = 0.0;
	m_fAZVideoTicks = 0.0;
	m_fAZVideo_mod = 0.0;
}

CPlatform::~CPlatform()
{
	ClearDevicesVector();
}

void CPlatform::ClearDevicesFounds()
{
	m_nFoundFlags = 0;
	m_pBoard = nullptr;
	m_pMSTD = nullptr;
	m_pFDD = nullptr;
	m_pIRPS = nullptr;
	m_pAZBK = nullptr;
}

void CPlatform::ClearDevicesVector()
{
	ClearDevicesFounds();
	m_pMPIDevices.clear();
}

CBK001001 *CPlatform::GetBoardPtr()
{
	if (!(m_nFoundFlags & FOUND_BOARD)) // искали уже?
	{
		m_pBoard = nullptr;

		// нет, поищем
		for (auto &p : m_pMPIDevices)
		{
			// если нашли
			if (static_cast<DWORD>(p->GetBoardModel()) & BK_BOARD_TYPE_MDL)
			{
				m_pBoard = dynamic_cast<CBK001001 *>(p.get()); // сохраним
				break;
			}
		}

		m_nFoundFlags |= FOUND_BOARD;
	}

	return m_pBoard;
}

CFDDStd326 *CPlatform::GetFDDPtr()
{
	if (!(m_nFoundFlags & FOUND_FDD)) // искали уже?
	{
		m_pFDD = nullptr;

		// нет, поищем
		for (auto &p : m_pMPIDevices)
		{
			// если нашли
			if (static_cast<DWORD>(p->GetBoardModel()) & BK_FDD_TYPE_MDL)
			{
				m_pFDD = dynamic_cast<CFDDStd326 *>(p.get()); // сохраним
				break;
			}
		}

		m_nFoundFlags |= FOUND_FDD;
	}

	return m_pFDD;
}

CBKMSTD10 *CPlatform::GetMSTDPtr()
{
	if (!(m_nFoundFlags & FOUND_MSTD)) // искали уже?
	{
		m_pMSTD = nullptr;

		// нет, поищем
		for (auto &p : m_pMPIDevices)
		{
			// если нашли
			if (static_cast<DWORD>(p->GetBoardModel()) & BK_MSTD_TYPE_MDL)
			{
				m_pMSTD = dynamic_cast<CBKMSTD10 *>(p.get()); // сохраним
				break;
			}
		}

		m_nFoundFlags |= FOUND_MSTD;
	}

	return m_pMSTD;
}

CIRPS *CPlatform::GetIRPSPtr()
{
	if (!(m_nFoundFlags & FOUND_IRPS)) // искали уже?
	{
		m_pIRPS = nullptr;

		// нет, поищем
		for (auto &p : m_pMPIDevices)
		{
			// если нашли
			if (p->GetBoardModel() == BK_DEV_MPI::IRPS)
			{
				m_pIRPS = dynamic_cast<CIRPS *>(p.get()); // сохраним
				break;
			}
		}

		m_nFoundFlags |= FOUND_IRPS;
	}

	return m_pIRPS;
}

CAZBK *CPlatform::GetAZBKPtr()
{
	if (!(m_nFoundFlags & FOUND_AZBK)) // искали уже?
	{
		m_pAZBK = nullptr;

		// нет, поищем
		for (auto &p : m_pMPIDevices)
		{
			// если нашли
			if (p->GetBoardModel() == BK_DEV_MPI::AZBK)
			{
				m_pAZBK = dynamic_cast<CAZBK *>(p.get()); // сохраним
				break;
			}
		}

		m_nFoundFlags |= FOUND_AZBK;
	}

	return m_pAZBK;
}

// функции-члены CPlatform

void CPlatform::InitEmulator()
{
	// параметры экрана
	GetBoardPtr()->ChangePalette(); // палитру надо устанавливть тоже тут, потому что как оказалось,
	// если в загружаемом состоянии другой рендер экрана, он сбрасывает номер палитры, который мы только что перед этим устанавливаем.
	m_pScreen->SetSmoothing(g_Config.m_bSmoothing);
	m_pScreen->SetColorMode(g_Config.m_bColorMode);
	m_pScreen->SetAdaptMode(g_Config.m_bAdaptBWMode);
	m_pScreen->SetLuminoforeEmuMode(g_Config.m_bLuminoforeEmulMode);
	g_Config.m_bFullscreenMode ? m_pScreen->SetFullScreenMode() : m_pScreen->SetWindowMode();
	// инициализация текущих настроек
	m_pSpeaker->EnableSound(g_Config.m_bSpeaker);
	m_pSpeaker->SetFilter(g_Config.m_bSpeakerFilter);
	m_pSpeaker->SetDCOffsetCorrect(g_Config.m_bSpeakerDCOffset);
	m_pCovox->EnableSound(g_Config.m_bCovox);
	m_pCovox->SetFilter(g_Config.m_bCovoxFilter);
	m_pCovox->SetStereo(g_Config.m_bStereoCovox);
	m_pCovox->SetDCOffsetCorrect(g_Config.m_bCovoxDCOffset);
	m_pMenestrel->EnableSound(g_Config.m_bMenestrel);
	m_pMenestrel->SetFilter(g_Config.m_bMenestrelFilter);
	m_pMenestrel->SetDCOffsetCorrect(g_Config.m_bMenestrelDCOffset);
	m_pMenestrel->SetStereo(true); // всегда стерео
	m_pAYSnd->EnableSound(g_Config.m_bAY8910);
	m_pAYSnd->SetFilter(g_Config.m_bAY8910Filter);
	m_pAYSnd->SetDCOffsetCorrect(g_Config.m_bAY8910DCOffset);
	m_pAYSnd->SetStereo(true); // всегда стерео
	m_pTape->SetWaveParam(g_Config.m_nSoundSampleRate, BUFFER_CHANNELS);
	m_sMTV.nMediaTickCount = 0;
	m_sMTV.pBufPos = m_sMTV.pSoundBuffer.get();
}

// выносим это в отдельную процедуру, т.к. есть вероятность неудачного прочтения MSF,
// при этом надо восстановить всё как было раньше
void CPlatform::InitObjects()
{
	const int nMtc = m_pSound->ReInit(g_Config.m_nSoundSampleRate);   // пересоздаём звук с новыми параметрами, на выходе - длина медиафрейма в сэмплах
	m_pSpeaker->ReInit(); // ещё надо переинициализировать устройства, там
	m_pSpeaker->ConfigureTapeBuffer(nMtc);    // переопределяем буферы в зависимости от текущей частоты дискретизации
	m_pCovox->ReInit();   // есть вещи, зависящие от частоты дискретизации,
	m_pMenestrel->ReInit();
	m_pAYSnd->ReInit();  // которая теперь величина переменная. Но требует перезапуска конфигурации.

	// переменные для медиа системы
	if (m_sMTV.nMediaTicksPerFrame != nMtc)
	{
		m_sMTV.nMediaTicksPerFrame = nMtc;

		if (m_sMTV.pSoundBuffer)
		{
			m_sMTV.pSoundBuffer.reset();
		}

		m_sMTV.pSoundBuffer = std::make_unique<SAMPLE_INT[]>(size_t(m_sMTV.nMediaTicksPerFrame) * BUFFER_CHANNELS);
		m_sMTV.pBufPos = m_sMTV.pSoundBuffer.get();

		if (!m_sMTV.pSoundBuffer)
		{
			g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		}
	}

	// установка размера буфера для осциллографа.
	m_pMainFrame->SendMessage(WM_OSC_SETBUFFER, WPARAM(nMtc));
	// если в ини файле задана частота, то применим её, вместо частоты по умолчанию.
	NormalizeCPU();
}


bool CPlatform::StartPlayTape() const
{
	return StartPlayTape(g_Config.m_strTapePath / m_cli.strTapeFileName);
}

bool CPlatform::StartPlayTape(const fs::path &strPath) const
{
	const CString strExt = strPath.extension().c_str();
	TAPE_FILE_INFO tfi{};
	memset(&tfi, -1, sizeof(TAPE_FILE_INFO));

	if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE))))
	{
		m_pTape->LoadWaveFile(strPath);
	}
	else if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE))))
	{
		m_pTape->LoadMSFFile(strPath);
	}
	else if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY))))
	{
		m_pTape->LoadBinFile(strPath, &tfi);
	}
	else
	{
		return false;
	}

	m_pTape->GetWaveFile(&tfi); // вычисляем и заполняем внутренние переменные
	m_pTape->SetWaveLoaded(true);
	return true;
}

void CPlatform::StopBoard()
{
	StopCPU(); // остановка CPU - там прекращается вызов фреймов и поток работает вхолостую
	StopTimerThread(); // остановка и завершение потока.
}

void CPlatform::StartBoard()
{
	StartTimerThread();
	RunCPU();
}


// UIшная функция, выполняемая при нажатии кнопки Reset
void CPlatform::ResetBoard(bool &bLongreset, const bool bIsSuPress)
{
	const auto pBoard = GetBoardPtr();

	if (pBoard && m_bRunning.IsLocked()) // защита от множественного вызова функции самой из себя.
	{
		m_pScript->StopScript(); //выполнение скрипта прекратим. А то фигня какая-то получается.
		StopCPU();
		const BK_DEV_MPI fdd_model = (GetFDDPtr()) ? GetFDDPtr()->GetBoardModel() : BK_DEV_MPI::NONE;

		// если контроллер А16М и сделали длинный ресет или контроллер СМК512 - делаем перезапуск
		// с адреса, который задаётся контроллером.
		if (
		    ((fdd_model == BK_DEV_MPI::A16M) && bLongreset) ||
		     (fdd_model == BK_DEV_MPI::SMK512)
		)
		{
			const auto a16m = dynamic_cast<CFDD_A16M *>(GetFDDPtr());
			ASSERT(a16m);

			if (a16m)
			{
				a16m->SetAltProMode(ALTPRO_A16M_START_MODE);
				a16m->SetAltProCode(0);
			}

			// если задан флаг СУ/ресет - перезапуск по 100000, для БК10 ни на что не влияет,
			// для БК11 - сбрасывает 14 бит, получая адрес запуска 100000.
			if (bIsSuPress)
			{
				pBoard->ResetCold(040000);
			}
			else
			{
				pBoard->ResetCold(0);
			}
		}
		else
		{
			const auto pAZBK = GetAZBKPtr();

			if (pAZBK)
			{
				pAZBK->NextTick(); // перед ресетом БК нужно установить флаг инициализации.
				// иначе стартует не с того адреса.
			}

			// если задан флаг СУ/ресет - перезапуск по 100000, для БК10 ни на что не влияет,
			// для БК11 - сбрасывает 14 бит, получая адрес запуска 100000.
			if (bIsSuPress)
			{
				pBoard->ResetCold(040000);
			}
			else
			{
				pBoard->ResetCold(0);
			}

			// СУ/ресет не реализовывается на контроллере СМК512 потому, что всегда подменяется
			// содержимое 177716 по чтению наложением ПЗУ.
			// А на А16М при коротком ресете и на обычных КНГМД СУ/ресет работает

			if (pAZBK)  // Для AZBK надо делать hard reset после хард ресета БКшки
			{
				pAZBK->ResetCold();
			}
		}

		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 1);    // тут делается инициализация обработчика клавиатуры
		bLongreset = false;
		// Запускаем CPU
		RunCPU();

		// если установлен флаг остановки после создания, приостановим выполнение
		if (g_Config.m_bPauseCPUAfterStart)
		{
			BreakCPU();
		}
	}
}

bool CPlatform::InitPlatform()
{
	for (auto &p : m_pMPIDevices)  // пройдёмся по вектору
	{
		if (p) // если есть устройство
		{
			if (!p->InitDevice(g_Config.m_nCPURunAddr))
			{
				return false;
			}
		}
	}

	// если ресет не удался - значит не удалось проинициализировать
	// память - значит не удалось загрузить какие-то дампы ПЗУ -
	// значит дальше работать невозможно.
	m_nCPUTicks = 0;
	m_sOSCV.clear();
	m_fAZVideoTicks = 0.0;
	m_fMemoryTicks = 0.0;
	m_fMediaTicks = 0.0;
	m_fFDDTicks = 0.0;
	m_nCPUFreq_prev = 0;
	FrameParam();
	m_pCPU->InitCPU();
	m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 1);    // тут делается инициализация обработчика клавиатуры
	// ещё косяк - в функции помимо инициализации делается ресет устройства.
	// а в функции InitCPU вызывается ResetPlatform, который делает только ресет устройства
	// получается дублирование ресетов, лишнее действие.
	// хоть это не критично, и эти функции делались немного для разных целей, ресет нужен
	// и там, и там, но вот вместе они дают такой эффект.
	// приаттачим все образы дискет.
	auto pFDD = GetFDDPtr();

	if (pFDD)
	{
		pFDD->AttachDrives();
	}

	return true;
}

void CPlatform::ResetPlatform() const
{
	for (auto &p : m_pMPIDevices)  // пройдёмся по вектору
	{
		if (p) // если есть устройство
		{
			p->Reset(); // общий ресет нужно делать здесь.
			// после того, как будет проинициализирована периферия.
		}
	}
}


void CPlatform::ClearProcessingFiles()
{
	m_cli.clear();
}

bool CPlatform::DropFiles(CString *strPath)
{
	if (strPath)
	{
		ClearProcessingFiles();
		const fs::path ps = strPath->GetString();
		const CString strExt = ps.extension().c_str();
		TCHAR command = 0;

		if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY))))  // Binary file
		{
			command = _T('B');
		}
		else if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_MEMORYSTATE))))  // Memory state file
		{
			command = _T('M');
		}
		else if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE)))
		         || !strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE))))  // BK tape file
		{
			command = _T('T');
		}
		else if (!strExt.CollateNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_SCRIPT))))  // Initial Script file
		{
			command = _T('S');
		}
		else
		{
			ASSERT(false);
			return false;
		}

		SetCmdLParameters(command, *strPath);
		return true;
	}

	return false;
}

void CPlatform::ParseCommandLineParameters(CString strCommands)
{
	ClearProcessingFiles();
	int nFindPos = strCommands.Find(_T('/'));

	if (nFindPos == -1)
	{
		return; // ключей нету, разбирать нечего, просто выходим.
	}

	// если ключи есть, оставляем только их.
	strCommands = strCommands.Right(strCommands.GetLength() - max(0, nFindPos));

	while (int nStrLen = strCommands.GetLength()) //nStrLen - текущая длина оставшейся неразобранной строки
	{
		nFindPos = strCommands.Find(_T('/'), 1); // ищем, есть ли ещё ключи

		if (nFindPos == -1) // если нету
		{
			nFindPos = nStrLen; // то всё что есть - параметр данного ключа
		}

		CString strParam = strCommands.Left(nFindPos); // выделяем параметр в отдельную строку
		strCommands = strCommands.Right(nStrLen - nFindPos); // и отсекаем их от обрабатываемой строки.
		strParam.Trim(); // уберём пробелы, если есть
		TCHAR command = toupper(strParam[1]); // получим значение ключа
		strParam = strParam.Right(strParam.GetLength() - 2); // выделим аргументы
		strParam.Trim(); // снова уберём пробелы. теперь они точно есть
		//if (command != _T('S'))  это если будем передавать аргументы скрипта, кавычки надо будет удалять там.
		{
			strParam.Trim(_T('\"')); // а ещё кавычки, если есть
		}

		if (!SetCmdLParameters(command, strParam))
		{
			switch (command)
			{
				case _T('?'):
				case _T('H'):
				{
					CString strTitle(MAKEINTRESOURCE(IDS_EMUL_TITLE));
					CString strPrompt(MAKEINTRESOURCE(IDS_COMMAND_PROMPT_INFO));
					g_BKMsgBox.Show(strTitle + strPrompt, MB_OK);
					return;
				}

				case _T('W'):
					// не знаю что такое, так и было
					break;
			}
		}
	}
}

bool CPlatform::SetCmdLParameters(TCHAR command, CString &strParam)
{
	switch (command)
	{
		case _T('B'): // Binary file
			if (!(m_cli.nStatus & CLI_KEY_D))
			{
				fs::path sp = strParam.GetString();
				// если задан ключ /D, ключ /B игнорируем
				m_cli.strBinFileName = sp.filename();

				if (sp.has_filename())
				{
					m_cli.nStatus |= CLI_KEY_B;

					if (sp.has_parent_path())
					{
						g_Config.m_strBinPath = sp.parent_path();
					}
				}
			}

			break;

		case _T('D'):
		{
			m_cli.nStatus &= ~CLI_KEY_B; // если был задан ключ /B, то его снимаем.
			fs::path sp = strParam.GetString();
			m_cli.strBinFileName = sp.filename();

			if (sp.has_filename())
			{
				m_cli.nStatus |= CLI_KEY_D;

				if (sp.has_parent_path())
				{
					g_Config.m_strBinPath = sp.parent_path();
				}
			}
		}
		break;

		case _T('M'): // Memory state file
		{
			fs::path sp = strParam.GetString();
			m_cli.strMemFileName = sp.filename();

			if (sp.has_filename())
			{
				m_cli.nStatus |= CLI_KEY_M;

				if (sp.has_parent_path())
				{
					g_Config.m_strMemPath = sp.parent_path();
				}
			}
		}
		break;

		case _T('T'): // BK tape file
		{
			fs::path sp = strParam.GetString();
			m_cli.strTapeFileName = sp.filename();

			if (sp.has_filename())
			{
				m_cli.nStatus |= CLI_KEY_T;

				if (sp.has_parent_path())
				{
					g_Config.m_strTapePath = sp.parent_path();
				}
			}
		}
		break;

		case _T('S'): // Initial Script file
		{
			fs::path sp = strParam.GetString();
			m_cli.strScriptFileName = sp.filename();

			if (sp.has_filename())
			{
				m_cli.nStatus |= CLI_KEY_S;

				if (sp.has_parent_path())
				{
					g_Config.m_strScriptsPath = sp.parent_path();
				}
			}

			// потенциально, за скриптом можно передавать список агрументов, которые будут передаваться скрипту
			// name.bkscript arg1,arg2,"string arg3",arg4
			// но тут сильно всё усложняется тогда и пока не ясно как ошибки обрабатывать.
			// запятая - разделитель аргументов, как быть, если запятая - символ в составе аргумента?
			// и как быть, если слеш -тоже символ в составе аргумента?
			// и обработка кавычек тоже фиг знает как делается. Т.е. как деляется - ясно, нужно писать большой сложный
			// парсер строки, а хочется, чтобы кто-то за меня это сделал.
		}
		break;

		case _T('C'):
			g_Config.SetBKBoardType(strParam);
			m_cli.nStatus |= CLI_KEY_C;
			break;

		case _T('P'):
			// /P nn:nn
		{
			m_cli.nStatus |= CLI_KEY_P;
			int nDlmPos = strParam.Find(_T(':')); // ищем, есть ли разделитель

			if (nDlmPos != -1) // если есть
			{
				if (nDlmPos > 0) // и не самый первый
				{
					m_cli.strPage0 = strParam.Left(nDlmPos).Trim();
					m_cli.nStatus |= CLI_KEY_P_PAGE0;
				}

				strParam = strParam.Right(strParam.GetLength() - nDlmPos - 1);

				if (strParam.GetLength() > 0)
				{
					m_cli.strPage1 = strParam.Trim();
					m_cli.nStatus |= CLI_KEY_P_PAGE1;
				}
			}
			else //если нету, то значит только первое окно
			{
				if (strParam.GetLength() > 0)
				{
					m_cli.strPage0 = strParam.Trim();
					m_cli.nStatus |= CLI_KEY_P_PAGE0;
				}
			}
		}
		break;

		case _T('L'):   // задать адрес загрузки файла дампа
			if (!strParam.IsEmpty())
			{
				m_cli.nLoadAddr = Global::OctStringToWord(strParam);
				m_cli.nStatus |= CLI_KEY_L;
			}

			break;

		case _T('A'):   // задать адрес запуска файла дампа, если он отличается от адреса загрузки
			if (!strParam.IsEmpty())
			{
				m_cli.nStartAddr = Global::OctStringToWord(strParam);
				m_cli.nStatus |= CLI_KEY_A;
			}

			break;

		case _T('R'):
			m_cli.nStatus |= CLI_KEY_R;
			break;

		case _T('F'):
			m_cli.nStatus |= CLI_KEY_F;
			break;

		default:
			return false;
	}

	return true;
}


void CPlatform::SetScript(const bool bXlat) const
{
	m_pScript->SetScript(g_Config.m_strScriptsPath, m_cli.strScriptFileName, bXlat);
}

void CPlatform::StopAll()
{
	m_pMainFrame->PostMessage(WM_STOP_MAINLOOPTIMER);
	StopBoard();
}

void CPlatform::StartAll()
{
	StartBoard();
	m_pMainFrame->PostMessage(WM_START_MAINLOOPTIMER);
}


bool CPlatform::CreateDevice(const BK_DEV_MPI device)
{
	std::unique_ptr<CBKMPIDevice> pDev;
	uint32_t nPlatformStatus = 0;
	switch (device)
	{
		case BK_DEV_MPI::NONE:
			break;

		case BK_DEV_MPI::BK0010:
			pDev = std::make_unique<CBK0010>();
			break;

		case BK_DEV_MPI::BK001001:
			pDev = std::make_unique<CBK001001>();
			break;

		case BK_DEV_MPI::BK0011:
			pDev = std::make_unique<CBK0011>();
			nPlatformStatus |= CONFPLATFORM_11M;
			break;

		case BK_DEV_MPI::BK0011M:
			pDev = std::make_unique<CBK0011M>();
			nPlatformStatus |= CONFPLATFORM_11M;
			break;

		case BK_DEV_MPI::MSTD10:
			pDev = std::make_unique<CBKMSTD10>();
			break;

		case BK_DEV_MPI::MSTD10_EXT32:
			pDev = std::make_unique<CBKMSTD10_EXT32>();
			break;

		case BK_DEV_MPI::MSTD11:
			pDev = std::make_unique<CBKMSTD11>();
			break;

		case BK_DEV_MPI::MSTD11M:
			pDev = std::make_unique<CBKMSTD11M>();
			break;

		case BK_DEV_MPI::STD_326:
			pDev = std::make_unique<CFDDStd326>();
			nPlatformStatus |= CONFPLATFORM_FDD;
			break;

		case BK_DEV_MPI::STD_253:
			pDev = std::make_unique<CFDDStd253>();
			nPlatformStatus |= CONFPLATFORM_FDD;
			break;

		case BK_DEV_MPI::STD_MEM:
			pDev = std::make_unique<CFDDStd326Mem>();
			nPlatformStatus |= CONFPLATFORM_FDD;
			break;

		case BK_DEV_MPI::A16M:
			pDev = std::make_unique<CFDD_A16M>();
			nPlatformStatus |= CONFPLATFORM_FDD | CONFPLATFORM_ALTPRO;
			break;

		case BK_DEV_MPI::SMK512:
			pDev = std::make_unique<CFDD_SMK512>();
			nPlatformStatus |= CONFPLATFORM_FDD | CONFPLATFORM_ALTPRO | CONFPLATFORM_HDD;
			break;

		case BK_DEV_MPI::SAMARA:
			pDev = std::make_unique<CFDD_Samara>();
			nPlatformStatus |= CONFPLATFORM_FDD | CONFPLATFORM_HDD;
			break;

		case BK_DEV_MPI::IRPS:
			pDev = std::make_unique<CIRPS>();
			nPlatformStatus |= CONFPLATFORM_IRPS;
			break;

		case BK_DEV_MPI::AZBK:
			pDev = std::make_unique<CAZBK>();
			nPlatformStatus |= CONFPLATFORM_AZBK | CONFPLATFORM_ALTPRO;
			break;

		case BK_DEV_MPI::OPTOK:
			pDev = std::make_unique<CFDDOptok>();
			nPlatformStatus |= CONFPLATFORM_OPTOK;
			break;

		default:
			ASSERT(false);
	}

	if (pDev)
	{
		m_nPlatformStatus |= nPlatformStatus;
		pDev->InitialSetDevice();
		m_pMPIDevices.push_back(std::move(pDev));
		return true;
	}

	return false;
}

bool CPlatform::CreateBoard(CONF_BKMODEL nConf)
{
	m_nPlatformStatus = 0;
	m_vWindows.clear();
	g_Config.SetBKModel(nConf);
	// создадим новую конфигурацию
	bool bRes = CreateDevice(g_Config.GetBKBoardModel());

	if (bRes)
	{
		// теперь создадим доп устройство.
		// TODO: теоретически их может быть несколько, а может и вообще не быть
		bRes = CreateDevice(g_Config.GetMPIDeviceModel());

		if (g_Config.m_bEnableIRPS)
		{
			// теперь создадим блок ИРПС
			bRes = CreateDevice(BK_DEV_MPI::IRPS);
		}

		return true;
	}

	// если не создалась мат. плата - то лучше ничего дальше не делать
	return false;
}

void CPlatform::DeleteBoard()
{
	if (GetBoardPtr())
	{
		// Если конфигурация уже существует, удалим её
		StopAll();  // сперва всё остановим
		g_Config.SaveConfig();
	}

	ClearDevicesVector();
	m_nPlatformStatus = 0;
}

void CPlatform::RunBoard()
{
	StartAll();

	// если не установлен флаг остановки после создания
	if (g_Config.m_bPauseCPUAfterStart)
	{
		BreakCPU();
	}
}


void CPlatform::RunOver()
{
	// Run one command with go over command
	const uint16_t pc = m_pCPU->GetRON(CCPU::REGISTER::PC);
	const uint16_t instr = GetWordIndirect(pc);

	if (m_pDebugger->IsInstructionOver(instr))
	{
		RunToAddr(pc + m_pDebugger->CalcInstructionLength(instr));
		return;
	}

	uint16_t NextAddr = m_pDebugger->CalcNextAddr(pc);

	if (NextAddr != BREAK_ADDRESS::ADDRESS_NONE)
	{
		RunToAddr(NextAddr);
	}
	else
	{
		RunInto();
	}
}

void CPlatform::RunToAddr(uint16_t addr) const
{
	// Run all commands to address
	UnbreakCPU(addr);
}


void CPlatform::RunInto() const
{
	// Run one command with go into command
	UnbreakCPU(BREAK_ADDRESS::GO_INTO);
}


void CPlatform::RunOut() const
{
	// Run all commands to function end
	UnbreakCPU(BREAK_ADDRESS::GO_OUT);
}


void CPlatform::BreakCPU() const
{
	m_bBreaked.Lock(); // включаем отладочную приостановку
	CDebugger::InitOutMode();
	m_pMainFrame->PostMessage(WM_CPU_DEBUGBREAK);
	m_pMainFrame->GetBreakpointViewPtr()->SendMessage(WM_DBG_BREAKPOINTS_UPDATE);
}

void CPlatform::UnbreakCPU(int nGoto) const
{
	m_pDebugger->DropShowCondBreakpoints();
	m_pMainFrame->GetBreakpointViewPtr()->SendMessage(WM_DBG_BREAKPOINTS_UPDATE);
	m_nGotoAddress = nGoto;
	m_bBreaked.UnLock(); // отменяем отладочную приостановку
}

void CPlatform::RunCPU(bool bUnbreak)
{
	if (!m_bRunning.IsLocked())
	{
		if (bUnbreak)
		{
			UnbreakCPU(BREAK_ADDRESS::ADDRESS_NONE);
		}

		// Set running flag
		m_bRunning.Lock();
	}
}


void CPlatform::StopCPU(bool bUnbreak) const
{
	if (m_bRunning.IsLocked()) // если работает, выполняем, если уже остановлен - то не надо
	{
		m_bRunning.UnLock();

		if (bUnbreak)
		{
			UnbreakCPU(BREAK_ADDRESS::ADDRESS_NONE); // отменяем отладочную приостановку, для случая, когда мы останавливаем
			// проц чтобы изменить конфигурацию, загрузить состояние или выйти из программы
		}

		MSG msg;
		BOOL bRet;

		while (!m_mutRunLock.try_lock()) // ждём конца фрейма
		{
			// использовать lock_guard или просто lock нельзя, потому что это
			// остановит очередь сообщений и UI полностью зависнет в дедлоке
			// Хотя оно как-то умудляется всё равно остановить очередь сообщений,
			// поэтому берём на себя трансляцию сообщений, чтобы UI не зависало
			// этот костыль всё-таки нужен. Без него при загрузке сохранения получается дедлок UI.
			if (bRet = GetMessage(&msg, m_pMainFrame->GetSafeHwnd(), 0, 0))
			{
				if (bRet != -1)
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
		}

		m_mutRunLock.unlock();
	}
}


uint8_t CPlatform::GetByte(const uint16_t addr) const
{
	int nTC = 0;
	return  GetByteT(addr, nTC);
}

uint8_t CPlatform::GetByteT(const uint16_t addr, int &nTC) const
{
	uint8_t b2 = 0;
	bool res = false;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			uint8_t b1;
			int ntc;
			bool cb = p->GetByteT(addr, &b1, ntc);
			res |= cb;
			b2 |= b1;

			if (cb)
			{
				nTC = max(nTC, ntc);
			}
		}
	}

	if (res)
	{
		return b2;
	}

	throw CExceptionHalt(addr, _T("Can't read this address."));
}

uint16_t CPlatform::GetWord(const uint16_t addr) const
{
	int nTC = 0;
	return GetWordT(addr, nTC);
}

uint16_t CPlatform::GetWordT(const uint16_t addr, int &nTC) const
{
	uint16_t w2 = 0;
	bool res = false;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			uint16_t w1;
			int ntc;
			bool cb = p->GetWordT(addr, &w1, ntc);
			res |= cb;
			w2 |= w1;

			if (cb)
			{
				nTC = max(nTC, ntc);
			}
		}
	}

	if (res)
	{
		return w2;
	}

	throw CExceptionHalt(addr, _T("Can't read this address."));
}

void CPlatform::SetByte(const uint16_t addr, uint8_t b) const
{
	int nTC = 0;
	SetByteT(addr, b, nTC);
}

void CPlatform::SetByteT(const uint16_t addr, uint8_t b, int &nTC) const
{
	bool res = false;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			int ntc;
			bool cb = p->SetByteT(addr, b, ntc);
			res |= cb;

			if (cb)
			{
				nTC = max(nTC, ntc);
			}
		}
	}

	if (!res)
	{
		throw CExceptionHalt(addr, _T("Can't write this address."));
	}
}


void CPlatform::SetWord(const uint16_t addr, uint16_t w) const
{
	int nTC = 0;
	SetWordT(addr, w, nTC);
}

void CPlatform::SetWordT(const uint16_t addr, uint16_t w, int &nTC) const
{
	bool res = false;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			int ntc;
			bool cb = p->SetWordT(addr, w, ntc);
			res |= cb;

			if (cb)
			{
				nTC = max(nTC, ntc);
			}
		}
	}

	if (!res)
	{
		throw CExceptionHalt(addr, _T("Can't write this address."));
	}
}


/*
GetByteIndirect,GetWordIndirect,SetByteIndirect,SetWordIndirect
этими функциями пользуются многие вспомогательные модули для того чтобы читать данные
напрямую из памяти, и как правило чтобы читать/писать что-то из/в регистров, а т.к.
внутренние системные регистры переехали в CPU, здесь делается перехват их адресов, чтобы
получить/записать значения из/в регистров в CPU
и да, тут не делаются проверки на невозможность, читать/писать в память, ибо эти функции
не CPU.
*/

uint8_t CPlatform::GetByteIndirect(const uint16_t addr) const
{
	if (0177700 <= addr && addr < 0177714)
	{
		uint16_t w = m_pCPU->GetSysRegsIndirect(addr);
		return (addr & 1) ? HIBYTE(w) : LOBYTE(w);
	}

	uint8_t b2 = 0;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			uint8_t b1 = 0;
			p->GetByteIndirect(addr, &b1);
			b2 |= b1;
		}
	}

	return b2;
}

uint16_t CPlatform::GetWordIndirect(const uint16_t addr) const
{
	if (0177700 <= addr && addr < 0177714)
	{
		uint16_t w = m_pCPU->GetSysRegsIndirect(addr);
		return w;
	}

	uint16_t w2 = 0;

	for (const auto &p : m_pMPIDevices)
	{
		if (p)
		{
			uint16_t w1 = 0;
			p->GetWordIndirect(addr, &w1);
			w2 |= w1;
		}
	}

	return w2;
}

void CPlatform::SetByteIndirect(const uint16_t addr, uint8_t b) const
{
	if (0177700 <= addr && addr < 0177714)
	{
		m_pCPU->SetSysRegs(addr, b);
	}
	else
	{
		for (const auto &p : m_pMPIDevices)
		{
			if (p)
			{
				p->SetByteIndirect(addr, b);
			}
		}
	}
}

void CPlatform::SetWordIndirect(const uint16_t addr, uint16_t w) const
{
	if (0177700 <= addr && addr < 0177714)
	{
		m_pCPU->SetSysRegs(addr, w);
	}
	else
	{
		for (const auto &p : m_pMPIDevices)
		{
			if (p)
			{
				p->SetWordIndirect(addr, w);
			}
		}
	}
}

void CPlatform::KeyboardInterrupt(const uint16_t interrupt)
{
	const auto board = GetBoardPtr();
	board->m_reg177660 |= 0200; // Установим состояние готовности в 177660
	if (!(board->m_reg177660 & 0100)) // если прерывания разрешены,
	{
		if (m_pAZBK && (interrupt == INTERRUPT_274 && board->m_reg177662in == 017))
		{
			m_pAZBK->CatchAr2Lat();
		}

		m_pCPU->InterruptVIRQ(interrupt); // делаем прерывание
	}
}

void CPlatform::VectorInterrupt(const uint16_t interrupt) const
{
	m_pCPU->InterruptVIRQ(interrupt);
}

int CPlatform::CalcStep() const
{
	int code = 0;

	if (::GetAsyncKeyState(VK_CONTROL) & 0x8000)
	{
		code |= (1 << 1);
	}

	if (::GetAsyncKeyState(VK_MENU) & 0x8000)
	{
		code |= (1 << 2);
	}

	if (::GetAsyncKeyState(VK_SHIFT) & 0x8000)
	{
		code |= (1 << 0);
	}

	int step;

	switch (code)
	{
		default:
		case 0: // ничего не нажато
			step = 1000;
			break;

		case 1: // shift
			step = 1;
			break;

		case 2: // ctrl
			step = 10;
			break;

		case 3: // crtl+shift
			step = 100;
			break;

		case 4: // alt
			step = 1000000;
			break;

		case 5: // alt+shift
			step = 10000;
			break;

		case 6: // ctrl+alt
			step = 100000;
			break;

		case 7: // ctrl+alt+shift
			step = 0; // мегакомбо
			break;
	}

	return step;
}

void CPlatform::AccelerateCPU()
{
	if (m_nCPUFreq < m_nHighBound)
	{
		const int n = CalcStep(); // если мегакомбо, для увеличения - увеличиваем на порядок

		if (n)
		{
			m_nCPUFreq += n;
		}
		else
		{
			m_nCPUFreq *= 10;
		}

		if (m_nCPUFreq > m_nHighBound)
		{
			m_nCPUFreq = m_nHighBound;
		}
	}
}

void CPlatform::SlowdownCPU()
{
	if (m_nCPUFreq > m_nLowBound)
	{
		const int n = CalcStep(); // если мегакомбо, для уменьшения - уменьшаем на порядок

		if (n)
		{
			m_nCPUFreq -= n;
		}
		else
		{
			m_nCPUFreq /= 10;
		}

		if (m_nCPUFreq < m_nLowBound)
		{
			m_nCPUFreq = m_nLowBound;
		}
	}
}

void CPlatform::NormalizeCPU()
{
	if (g_Config.m_nCPUFrequency)
	{
		SetCPUFreq(g_Config.m_nCPUFrequency);
	}
	else
	{
		ResetToCPUBaseFreq();
	}
}

bool CPlatform::CanAccelerate() const
{
	return (m_nCPUFreq < m_nHighBound);
}

bool CPlatform::CanSlowDown() const
{
	return (m_nCPUFreq > m_nLowBound);
}

int CPlatform::GetLowBound() const
{
	return m_nLowBound;
}

int CPlatform::GetHighBound() const
{
	return m_nHighBound;
}

void CPlatform::SetCPUBaseFreq(const int frq) // установка базовой частоты
{
	m_nCPUFreq = m_nBaseCPUFreq = frq;
}

void CPlatform::ResetToCPUBaseFreq()
{
	m_nCPUFreq = m_nBaseCPUFreq;
}

void CPlatform::SetCPUFreq(int frq) // установка текущей частоты
{
	if (frq < GetLowBound())
	{
		frq = GetLowBound();
	}
	else if (frq > GetHighBound())
	{
		frq = GetHighBound();
	}

	m_nCPUFreq = frq;
}

int CPlatform::GetCPUFreq() const // выдача текущей частоты
{
	return m_nCPUFreq;
}

int CPlatform::GetCPUSpeed() const // выдача текущей частоты для конфигурации
{
	return (m_nCPUFreq == m_nBaseCPUFreq) ? 0 : m_nCPUFreq;
}


void CPlatform::SetMemPages(const int pg0, const int pg1)
{
	GetBoardPtr()->SetMemPages(pg0, pg1);
}

void CPlatform::RestoreMemPages()
{
	GetBoardPtr()->RestoreMemPages();
}

// загрузка состояния эмулятора
bool CPlatform::LoadMemoryState(CMSFManager *pMsf)
{
	bool bRet = false;
	// временно выгрузим все образы дискет.
	auto pFDD = GetFDDPtr();

	if (pFDD)
	{
		pFDD->DetachDrives();
	}

	// Сохраняем старую конфигурацию
	std::vector<std::unique_ptr<CBKMPIDevice>> pOldMPIDevices = std::move(m_pMPIDevices);
	CONF_BKMODEL nOldConf = g_Config.GetBKModel();
	ClearDevicesVector();
	// сперва нужно узнать, что восстанавливать
	// поэтому придётся и тут прочитать блок MSF_BLOCKTYPE_PLATFORM
	std::unique_ptr<MSF_PLATFORM_DATA> pPD;

	if (RestorePlatform(pMsf, pPD)) // в любом случае, заполняем структуру MSF_PLATFORM_DATA
	{
		// !!! Здесь сделано допущение, могущее привести к серьёзной ошибке!
		// в MSF_PLATFORM_DATA порядок списка моделей устройств может не совпадать с порядком,
		// который создаётся в CreateBoard, и хоть пока это так и есть. Но так может быть не всегда.
		// Иначе, нужно будет переписать эту функцию вообще по-другому.
		if (CreateBoard(static_cast<CONF_BKMODEL>(pPD->nPlatformConfig)))
		{
			// присоединим устройства, чтобы хоть что-то было для выполнения ResetHot
			InitObjects();

			// Тут применим хитрый ход. Сперва восстановим конфигурацию,
			// а в функции RestoreState сделаем исключение, и там восстанавливать не будем.
			if (RestoreConfig(pMsf))
			{
				if (InitPlatform())
				{
					if (RestoreState(pMsf)) // там заново прочитается RestorePlatform и всё пойдёт по задуманному
					{
						// если всё удачно, вот только тогда и уничтожаем старую конфигурацию
						//pOldMPIDevices.clear();
						bRet = true;
					}
				}
			}
		}
		else
		{
			g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		}
	}

	if (!bRet)
	{
		// Неподдерживаемая конфигурация, или ошибка при создании
		g_BKMsgBox.Show(IDS_ERRMSF_WRONG, MB_OK);
		ClearDevicesVector(); // удаляем всё, что создавали и так и не создали
		m_pMPIDevices = std::move(pOldMPIDevices);
		pOldMPIDevices.clear();
		g_Config.SetBKModel(nOldConf);
		g_Config.LoadConfig(true, true);      // восстановим из ини файла параметры
	}

	// приаттачим все образы дискет.
	pFDD = GetFDDPtr();

	if (pFDD)
	{
		pFDD->AttachDrives();
	}

	return bRet;
}

// сохранение состояния эмулятора
bool CPlatform::SaveMemoryState(const fs::path &strPath)
{
	// остановим эмулятор
	m_pMainFrame->SendMessage(WM_STOP_MAINLOOPTIMER);
	StopCPU(false);
	// создадим и откроем файл.
	CMSFManager msf;

	if (!msf.OpenFile(strPath, false)) // открываем для записи
	{
		return false;
	}

	RestoreState(&msf); // универсальная процедура - для сохранения и восстановления
	// обратно запускаем эмулятор
	RunCPU(false);
	m_pMainFrame->SendMessage(WM_START_MAINLOOPTIMER);
	return true;
}

// универсальная процедура - для сохранения и восстановления
bool CPlatform::RestoreState(CMSFManager *pMsf)
{
	bool bRet = false;
	// 1. нужно сформировать блок MSF_BLOCKTYPE_PLATFORM
	std::unique_ptr<MSF_PLATFORM_DATA> pPD;

	if (RestorePlatform(pMsf, pPD)) // в любом случае, заполняем структуру MSF_PLATFORM_DATA
	{
		// 2. сохраняем превьюшку - скриншот экрана
		if (RestorePreview(pMsf))
		{
			// 3. сохраняем конфиг, при восстановлении - не вызываем функцию.
			if (pMsf->IsLoad() || RestoreConfig(pMsf))
			{
				// 4. сохраняем регистры CPU (тут, пока оно не самостоятельное устройство)
				if (RestoreCPURegisters(pMsf))
				{
					bRet = true;
					int n = 0;

					for (auto &p : m_pMPIDevices)
					{
						if (p)
						{
							DWORD id = pPD->pDevices[n++].nUniqueID;
							bRet = p->RestoreMemoryMap(id, pMsf); // необходимо делать строго перед работой с памятью.

							if (!bRet)
							{
								break;
							}

							bRet = p->RestoreMemory(id, pMsf);

							if (!bRet)
							{
								break;
							}

							bRet = p->RestoreRegisters(id, pMsf);

							if (!bRet)
							{
								break;
							}

							bRet = p->RestoreCustomData(id, pMsf);

							if (!bRet)
							{
								break;
							}
						}
					}
				}
			}
		}
	}

	return bRet;
}

// загрузка/сохранение параметров платформы
// в любом случае, заполняем структуру MSF_PLATFORM_DATA
bool CPlatform::RestorePlatform(CMSFManager *pMsf, std::unique_ptr<MSF_PLATFORM_DATA> &pPD) const
{
	pPD = std::make_unique<MSF_PLATFORM_DATA>();

	if (pPD)
	{
		if (pMsf->IsLoad())
		{
			// загрузка
			return pMsf->GetBlockPlatformData(pPD.get());
		}

		// сохранение
		// заполняем структуру, она переменного размера
		pPD->nPlatformConfig = static_cast<DWORD>(g_Config.GetBKModel());
		auto t = static_cast<DWORD>(m_pMPIDevices.size()); // размер вектора
		pPD->nDevNum = t; // пока сохраним результат
		pPD->pDevices.clear();
		int n = 0; // счётчик реально существующих устройств в векторе

		if (t) // если в векторе что-то есть
		{
			DWORD nUniqueID = 0x100;

			for (auto &p : m_pMPIDevices)  // пройдёмся по вектору
			{
				if (p) // если есть устройство
				{
					// сохраняем в вектор его тип и на всякий случай идентификатор
					pPD->pDevices.push_back({ static_cast<DWORD>(p->GetBoardModel()), nUniqueID++ });
					n++; // счётчик сосчитает устройство
				}

				// TODO: если вектор будет содержать null вместо указателя на устройство,
				// то надо будет скорректировать кол-во устройств
			}
		}

		pPD->nDevNum = n; // реальное количество устройств в списке, исключая null
		return pMsf->SetBlockPlatformData(pPD.get());
	}

	g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	return false;
}

bool CPlatform::RestorePreview(CMSFManager *pMsf) const
{
	bool bRet = true;

	if (pMsf->IsSave())
	{
		HBITMAP hScreenshot = m_pScreen->GetScreenshot();

		if (hScreenshot)
		{
			auto hBm = (HBITMAP)CopyImage(hScreenshot, IMAGE_BITMAP, 256, 256, LR_CREATEDIBSECTION);

			if (hBm)
			{
				bRet = pMsf->SetBlockPreviewEx(hBm, true);
				DeleteObject(hBm);
			}

			DeleteObject(hScreenshot);
		}
	}

	return bRet;
}

bool CPlatform::RestoreConfig(CMSFManager *pMsf)
{
	MSF_FRAMEDATA framedata{};
	if (pMsf->IsLoad())
	{
		pMsf->GetBlockConfig(); // если конфига нет, то остаётся текущий из ини файла

		if (pMsf->GetBlockFrameData(&framedata))
		{
			m_nCPUTicks = framedata.nCPUTicks;
			m_fMediaTicks = framedata.fMediaTicks;
			m_fMemoryTicks = framedata.fMemoryTicks;
			m_fFDDTicks = framedata.fFDDTicks;
			m_fAZVideoTicks = framedata.fAZVideoTicks;
			// вот это обязательно нужно сделать.
			// а то переинициализация фрейма делается опять не в то время, когда надо
			// с ещё старыми параметрами, а потом, с новыми уже не делается.
			m_nCPUFreq_prev = 0;
			FrameParam(); // пересчёт параметров фрейма от частоты
			m_pCPU->SetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
			m_sOSCV.nVideoAddress = framedata.nVideoAddress & 0xffff; // видео адрес, младшие 6 бит - счётчик строк внутри строки
			m_sOSCV.bHgate = (framedata.bHgate != 0); // флаг отсчёта служебных видеоциклов в строке
			m_sOSCV.bVgate = (framedata.bVgate != 0); // флаг отсчёта служебных строк
			m_sOSCV.nVGateCounter = framedata.nVGateCounter; // дополнительный счётчик служебных строк
			m_sOSCV.nLineCounter = framedata.nLineCounter & 0377; // счётчик видео строк
		}
		else
		{
			m_nCPUTicks = 0;
			m_fMediaTicks = 0.0;
			m_fMemoryTicks = 0.0;
			m_fFDDTicks = 0.0;
			m_fAZVideoTicks = 0.0;
			m_nCPUFreq_prev = 0;
			FrameParam(); // пересчёт параметров фрейма от частоты
			// состояние ВЕ таймера не меняется
			m_sOSCV.clear();
			// глюки и рассинхрон гарантированы.
		}

		return true; // если конфиг не прочёлся, значит этого блока просто нет. ничего страшного
	}

	bool bRet = pMsf->SetBlockConfigEx(true);

	framedata.nCPUTicks = m_nCPUTicks;
	framedata.fMediaTicks = m_fMediaTicks;
	framedata.fMemoryTicks = m_fMemoryTicks;
	framedata.fFDDTicks = m_fFDDTicks;
	framedata.fAZVideoTicks = m_fAZVideoTicks;
	m_pCPU->GetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
	framedata.nVideoAddress = m_sOSCV.nVideoAddress; // видео адрес, младшие 6 бит - счётчик строк внутри строки
	framedata.bHgate = m_sOSCV.bHgate ? 1 : 0; // флаг отсчёта служебных видеоциклов в строке
	framedata.bVgate = m_sOSCV.bVgate ? 1 : 0; // флаг отсчёта служебных строк
	framedata.nVGateCounter = m_sOSCV.nVGateCounter; // дополнительный счётчик служебных строк
	framedata.nLineCounter = m_sOSCV.nLineCounter; // счётчик видео строк
	return bRet && pMsf->SetBlockFrameData(&framedata);
}

bool CPlatform::RestoreCPURegisters(CMSFManager *pMsf) const
{
	MSF_CPU_REGISTERS cpu_reg{};

	if (pMsf->IsLoad())
	{
		if (!pMsf->GetBlockCPURegisters(&cpu_reg))
		{
			return false;
		}

		GetCPUPtr()->SetRON(CCPU::REGISTER::R0, cpu_reg.r0);
		GetCPUPtr()->SetRON(CCPU::REGISTER::R1, cpu_reg.r1);
		GetCPUPtr()->SetRON(CCPU::REGISTER::R2, cpu_reg.r2);
		GetCPUPtr()->SetRON(CCPU::REGISTER::R3, cpu_reg.r3);
		GetCPUPtr()->SetRON(CCPU::REGISTER::R4, cpu_reg.r4);
		GetCPUPtr()->SetRON(CCPU::REGISTER::R5, cpu_reg.r5);
		GetCPUPtr()->SetRON(CCPU::REGISTER::SP, cpu_reg.sp);
		GetCPUPtr()->SetRON(CCPU::REGISTER::PC, cpu_reg.pc);
		GetCPUPtr()->SetPSW(cpu_reg.psw);
	}
	else
	{
		cpu_reg.r0 = GetCPUPtr()->GetRON(CCPU::REGISTER::R0);
		cpu_reg.r1 = GetCPUPtr()->GetRON(CCPU::REGISTER::R1);
		cpu_reg.r2 = GetCPUPtr()->GetRON(CCPU::REGISTER::R2);
		cpu_reg.r3 = GetCPUPtr()->GetRON(CCPU::REGISTER::R3);
		cpu_reg.r4 = GetCPUPtr()->GetRON(CCPU::REGISTER::R4);
		cpu_reg.r5 = GetCPUPtr()->GetRON(CCPU::REGISTER::R5);
		cpu_reg.sp = GetCPUPtr()->GetRON(CCPU::REGISTER::SP);
		cpu_reg.pc = GetCPUPtr()->GetRON(CCPU::REGISTER::PC);
		cpu_reg.psw = GetCPUPtr()->GetPSW();

		if (!pMsf->SetBlockCPURegisters(&cpu_reg))
		{
			return false;
		}
	}

	return true;
}

constexpr auto BK_NAMELENGTH = 16;   // Максимальная длина имени файла на БК - 16 байтов
constexpr auto BK_BMB10 = 0320;
constexpr auto BK_BMB10_ERRADDR = 0301;
constexpr auto BK_BMB10_CRCADDR = 0312;
constexpr auto BK_BMB10_ADDRESS = 2;
constexpr auto BK_BMB10_LENGTH = 4;
constexpr auto BK_BMB10_NAME = 6;
constexpr auto BK_BMB10_FOUND_ADDRESS = BK_BMB10_NAME + BK_NAMELENGTH;
constexpr auto BK_BMB10_FOUND_LENGTH = BK_BMB10_FOUND_ADDRESS + 2;
constexpr auto BK_BMB10_FOUND_NAME = BK_BMB10_FOUND_LENGTH + 2;

// для БК10.
bool CPlatform::EmulateLoadTape()
{
	/*
	 * Известные косяки: 1) для бейсиковских бин файлов удаляет расширение .bin, как исправить
	 * непонятно, потому что никак не узнать, что мы загружаем именно бейсиковский бин файл,
	 * а не какой-либо другой. То же самое и с любыми другими файлами, по задумке авторов,
	 * имеющими расширение .bin, оно как правило удаляется.
	 * 2) при загрузке файла с автозапуском, автозапуск делается из ячейки на слово выше, чем в реальности.
	 * Это может быть критично только для очень хитрых программ.
	 **/
	// если включена эмуляция и по этому адресу действительно ПЗУ монитора БК10
	if (g_Config.m_bEmulateLoadTape && ((GetWordIndirect(0116206) == 04767) && (GetWordIndirect(0116210) == 0426)))
	{
		bool bFileSelect = false; // что делать после диалога выбора
		bool bCancelSelectFlag = false; // флаг для усложнения алгоритма
		bool bError = false;
		bool bIsDrop = false;
		CString strBinExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
		uint16_t fileAddr = 0;
		uint16_t abp = BK_BMB10;
		fs::path strFileName;
		// Внутренняя загрузка на БК
		uint8_t bkName[BK_NAMELENGTH] = { 0 };  // Максимальная длина имени файла на БК - BK_NAMELENGTH байтов
		uint8_t bkFoundName[BK_NAMELENGTH] = { 0 };

		if (!isBinFileNameSet())
		{
			// если загружаем не через драг-н-дроп, то действуем как обычно
			abp = m_pCPU->GetRON(CCPU::REGISTER::R1);       // получим адрес блока параметров из R1 (BK emt 36)
			fileAddr = GetWord(abp + BK_BMB10_ADDRESS);   // второе слово - адрес загрузки/сохранения

			// Подбираем BK_NAMELENGTH байтовое имя файла из блока параметров
			for (uint16_t c = 0; c < BK_NAMELENGTH; ++c)
			{
				bkName[c] = GetByte(abp + BK_BMB10_NAME + c);
			}

			strFileName = Global::BKToUNICODE(bkName, BK_NAMELENGTH).Trim().GetString(); // тут надо перекодировать  имя файла из кои8 в unicode

			if (!strFileName.empty()) // если имя файла не пустое
			{
				strFileName += strBinExt.GetString(); // добавляем стандартное расширение для бин файлов,
				// чтобы не рушить логику следующих проверок
			}
		}
		else
		{
			// если загружаем через драг-н-дроп, то берём имя оттуда
			strFileName = GetStrBinFileName();
			bIsDrop = true;
		}

		if (strFileName.empty())
		{
			// Если имя пустое - то покажем диалог выбора файла.
			bFileSelect = false;
l_SelectFile:
			// Запомним текущую директорию
			fs::path strCurDir = fs::current_path();
			fs::current_path(g_Config.m_strBinPath);
			CString strFilterBin(MAKEINTRESOURCE(IDS_FILEFILTER_BIN));
			CFileDialog dlg(TRUE, nullptr, nullptr,
			                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
			                strFilterBin, m_pScreen->GetBackgroundWindow());
			// Зададим начальной директорией директорию с Bin файлами
			dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath.c_str();

			if (dlg.DoModal() == IDCANCEL)
			{
				// Если нажали Отмену, установим ошибку во втором байте блока параметров
				SetByte(BK_BMB10_ERRADDR, 4);
				bError = true; // случилась ошибка
			}
			else
			{
				strFileName = dlg.GetPathName().GetString(); // вот выбранный файл
				g_Config.m_strBinPath = strFileName.parent_path();
				// имя файла надо бы как-то поместить в 0352..0372 иначе некоторые глюки наблюдаются
				CString strFound = dlg.GetFileTitle();
				Global::UNICODEtoBK(strFound, bkFoundName, BK_NAMELENGTH, true); // вот из этого массива будем потом помещать

				if (bFileSelect)
				{
					// тут надо проверить тот ли файл нам подсовывают.
					CString strFindEx = Global::BKToUNICODE(bkName, BK_NAMELENGTH); // с расширением
					fs::path p = strFindEx.GetString();
					CString strFind = p.stem().c_str(); // без расширения

					if (!bIsDrop) // только если не дроп. там не с чем сравнивать
					{
						if (strFind.CollateNoCase(strFound) != 0 && strFindEx.CollateNoCase(strFound) != 0)
						{
							int result = g_BKMsgBox.Show(IDS_BK_ERROR_WRONGFILE, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);

							if (result == IDYES)
							{
								goto l_SelectFile;
							}
							else
							{
								SetByte(BK_BMB10_ERRADDR, 4);
								bError = true;
								bCancelSelectFlag = true; // не будем ничего делать, сразу выйдем.
							}
						}
					}
				}
			}

			// восстановим текущую директорию
			fs::current_path(strCurDir);
		}
		else    // Если имя не пустое
		{
			// If Saves Default flag is set loading from User directory
			// Else Load from Binary files directory
			fs::path strCurrentPath = g_Config.m_bSavesDefault ? g_Config.m_strSavesPath : g_Config.m_strBinPath;
			CString s = strFileName.c_str();
			Global::SetSafeName(s); // перекодируем небезопасные символы на безопасные
			strFileName = s.GetString();

			// сейчас узнаем, нужно ли нам добавлять расширение .bin, или наоборот, удалять
			if (CString(strFileName.extension().c_str()).CollateNoCase(strBinExt) == 0)
			{
				// у файла уже есть расширение бин
				if (!fs::exists(strCurrentPath / strFileName)) // если нету файла с расширением.
				{
					fs::path str = strFileName.stem(); // удаляем расширение

					if (fs::exists(strCurrentPath / str)) // если есть файл без расширения
					{
						strFileName = str; // оставим файл без расширения
					}
					else
					{
						// нет файла ни с расширением, ни без расширения
						SetByte(BK_BMB10_ERRADDR, 1);
						bError = true;
					}
				}
			}
			else
			{
				// у файла нету расширения бин
				if (!fs::exists(strCurrentPath / strFileName)) // если нету файла без расширения
				{
					fs::path str = strFileName;
					str += strBinExt.GetString(); // добавляем стандартное расширение для бин файлов.

					if (fs::exists(strCurrentPath / str))  // если есть файл с расширением
					{
						strFileName = str;
					}
					else
					{
						// нет файла ни с расширением, ни без расширения
						SetByte(BK_BMB10_ERRADDR, 1);
						bError = true;
					}
				}
			}

			CString str = strFileName.stem().c_str();
			Global::UNICODEtoBK(str, bkFoundName, BK_NAMELENGTH, true);
			strFileName = strCurrentPath / strFileName;
		}

		if (!bCancelSelectFlag)
		{
			CFile file;
			uint16_t readAddr = 0;
			uint16_t readSize = 0;
			uint16_t loadAddr = 01000;
			uint16_t loadLen = 0;
			uint16_t loadcrc = 0;

			// Загрузим файл, если ошибок не было
			if (!bError && file.Open(strFileName.c_str(), CFile::modeRead))
			{
				file.Read(&readAddr, sizeof(readAddr));   // Первое слово в файле - адрес загрузки
				file.Read(&readSize, sizeof(readSize));   // Второе слово в файле - длина
				// сплошь и рядом встречаются .bin файлы. у которых во втором слове указана длина
				// меньше, чем длина файла - 4. Это другой формат бин, у которого в начале указывается
				// адрес, длина, имя файла[BK_NAMELENGTH], массив[длина], КС - контрольная сумма в конце
				uint16_t filesz = (file.GetLength() < 65536) ? static_cast<uint16_t>(file.GetLength()) : 65535;
				bool bIsCRC = false;

				if (readSize == filesz - 4)
				{
					bIsCRC = false;
				}
				else if (readSize == filesz - 6)
				{
					bIsCRC = true;
				}
				else if (readSize == filesz - 22)
				{
					bIsCRC = true;
					file.Read(bkFoundName, BK_NAMELENGTH); // прочитаем оригинальное имя файла
				}
				else
				{
					// всё равно загрузим. Пусть не бин
					file.Seek(0, CFile::SeekPosition::begin);
					readAddr = 0;
					readSize = filesz;
				}

				SetWord(abp + BK_BMB10_FOUND_ADDRESS, readAddr);
				SetWord(abp + BK_BMB10_FOUND_LENGTH, readSize);

				if (bkFoundName[0])
				{
					// копируем прочитанное имя файла
					for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
					{
						SetByte(abp + BK_BMB10_FOUND_NAME + i, bkFoundName[i]);
					}
				}
				else
				{
					// копируем прочитанное имя файла
					for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
					{
						SetByte(abp + BK_BMB10_FOUND_NAME + i, bkName[i]);
					}
				}

				if (fileAddr == 0)
				{
					fileAddr = readAddr;
				}

				//              if (fileAddr < 01000)
				//              {
				//                  // если файл с автозапуском, на всякий случай остановим скрипт
				//                  // если он выполнялся
				//                  m_pParent->GetScriptRunnerPtr()->StopScript();
				//              }
				SetWord(0264, fileAddr); loadAddr = fileAddr;
				SetWord(0266, readSize); loadLen = readSize;
				DWORD cs = 0; // подсчитаем контрольную сумму

				// Загрузка по адресу fileAddr
				for (int i = readSize; i > 0; i--)
				{
					uint8_t val;
					file.Read(&val, sizeof(val));
					SetByte(fileAddr++, val);
					cs += uint16_t(val);

					if (cs & 0xffff0000)
					{
						cs++;
						cs &= 0xffff;
					}
				}

				uint16_t crc;

				if (bIsCRC && file.Read(&crc, sizeof(crc)) == sizeof(uint16_t))
				{
					if (crc != LOWORD(cs))
					{
						SetByte(BK_BMB10_ERRADDR, 2);
						cs = crc;
					}
				}

				// а иначе, мы не знаем какая должна быть КС. поэтому считаем, что файл априори верный
				file.Close();
				// Заполняем системные ячейки, как это делает emt 36
				loadcrc = LOWORD(cs);
				SetWord(BK_BMB10_CRCADDR, loadcrc); // сохраним контрольную сумму
			}
			else
			{
				// При ошибке покажем сообщение
				CString strError;
				strError.Format(IDS_CANT_OPEN_FILE_S, strFileName.c_str());
				int result = g_BKMsgBox.Show(strError, MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON2);

				switch (result)
				{
					case IDNO:
						// если не хотим останавливаться, то пойдём на диалог, и поищем файл в другом месте.
						bError = false;
						SetByte(BK_BMB10_ERRADDR, 0);
						bFileSelect = true; // включим проверку на неподходящее имя.
						goto l_SelectFile;

					// если отмена - просто выходим с заданным кодом ошибки
					case IDYES:
						// если хотим остановиться - зададим останов.
						BreakCPU();
						[[fallthrough]];

					// если отмена - просто выходим с заданным кодом ошибки
					case IDCANCEL:
						SetByte(BK_BMB10_ERRADDR, 4);
						break;
				}
			}

			if (loadAddr < 0750)
			{
				// Помещаем в R1 последний адрес, куда производилось чтение, как в emt 36
				m_pCPU->SetRON(CCPU::REGISTER::R0, 0);
				m_pCPU->SetRON(CCPU::REGISTER::R1, loadAddr + loadLen);
				m_pCPU->SetRON(CCPU::REGISTER::R2, 0);
				m_pCPU->SetRON(CCPU::REGISTER::R3, 0177716);
				m_pCPU->SetRON(CCPU::REGISTER::R5, 040);
				m_pCPU->SetRON(CCPU::REGISTER::PC, 0117374); // выходим туда.
			}
			else
			{
				m_pCPU->SetRON(CCPU::REGISTER::R0, loadcrc);
				m_pCPU->SetRON(CCPU::REGISTER::R0, 0314);
				m_pCPU->SetRON(CCPU::REGISTER::R3, 0177716);
				m_pCPU->SetRON(CCPU::REGISTER::R4, 0);
				// Помещаем в R5 последний адрес, куда производилось чтение, как в emt 36
				m_pCPU->SetRON(CCPU::REGISTER::R5, loadAddr + loadLen);
				m_pCPU->SetRON(CCPU::REGISTER::PC, 0116710); // выходим туда.
			}
		}
		else
		{
			m_pCPU->SetRON(CCPU::REGISTER::PC, 0116214); // выходим туда.
		}

		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0);  // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

	return false; // не эмулируем
}


bool CPlatform::EmulateSaveTape() const
{
	// если включена эмуляция и по этому адресу действительно ПЗУ монитора БК10
	if (g_Config.m_bEmulateSaveTape && ((GetWordIndirect(0116170) == 04767) && (GetWordIndirect(0116172) == 062)))
	{
		// получим адрес блока параметров из R1 (BK emt 36)
		uint16_t abp = m_pCPU->GetRON(CCPU::REGISTER::R1);
		uint16_t fileAddr = GetWord(abp + BK_BMB10_ADDRESS); // второе слово - адрес загрузки/сохранения
		uint16_t fileSize = GetWord(abp + BK_BMB10_LENGTH);  // третье слово - длина файла (для загрузки может быть 0)
		uint16_t cs = 0;
		bool bError = SaveTapeFunc(abp, 0220, 0220, fileAddr, fileSize, cs);

		if (bError)
		{
			SetByte(BK_BMB10_ERRADDR, 4);
		}
		else
		{
			SetWord(BK_BMB10_CRCADDR, cs); // сохраним контрольную сумму на своё место
		}

		m_pCPU->SetRON(CCPU::REGISTER::PC, 0116402); // выходим туда.
		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0);  // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

	return false; // не эмулируем
}


constexpr auto BK_BMB11 = 040134;
constexpr auto BK_BMB11_PAGES = BK_BMB10_NAME + BK_NAMELENGTH;
constexpr auto BK_BMB11_FOUND_ADDRESS = BK_BMB11_PAGES + 2;
constexpr auto BK_BMB11_FOUND_LENGTH = BK_BMB11_FOUND_ADDRESS + 2;
constexpr auto BK_BMB11_FOUND_NAME = BK_BMB11_FOUND_LENGTH + 2;
constexpr auto BK_BMB11_ERRADDR = 052;
constexpr auto BK_BMB11_RNOFLAG = 040213;
constexpr auto BK_BMB11_FICTFLAG = 040214;
constexpr auto BK_BMB11_SYSPG = 043046;
constexpr auto BK_BMB11_CRCADDR = 040222;
constexpr auto BK_BMB11_FICTADDR = 040222;

const int CPlatform::m_arPageCodes[8] = { 6, 0, 2, 3, 4, 1, 7, 5 }; // перекодировка номеров страниц в коды.

// для БК11 В этих функциях много копипасты.
bool CPlatform::EmulateLoadTape11()
{
	if (g_Config.m_bEmulateLoadTape && ((GetWord(0143752) == 016706) && (GetWord(0143754) == 074240)))
	{
		uint16_t abp = BK_BMB11;
		// получим код для подключения страниц
		uint16_t nPageCode = GetWord(064) & 0204;
		int nWnd0 = GetByte(abp + BK_BMB11_PAGES) & 7;
		int nWnd1 = GetByte(abp + BK_BMB11_PAGES + 1) & 7;
		nPageCode |= (m_arPageCodes[nWnd0] << 12) | (m_arPageCodes[nWnd1] << 8) | 04000;
		LTF11Param p =
		{
			abp,
			nPageCode,                  // получим код для подключения страниц
			GetWord(BK_BMB11_SYSPG),    // код системной страницы БОС
			BK_BMB11_ERRADDR,
			BK_BMB11_CRCADDR,
			BK_BMB11_FICTADDR,
			0144026,
			0143752,
			!!GetByte(BK_BMB11_RNOFLAG), //прочитать только имя файла
			!!GetByte(BK_BMB11_FICTFLAG) //фиктивное чтение
		};
		bool bError = LoadTapeFunc11(p);
		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0); // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировал
	}

	return false;
}

bool CPlatform::EmulateSaveTape11() const
{
	if (g_Config.m_bEmulateSaveTape && ((GetWord(0143752) == 016706) && (GetWord(0143754) == 074240)))
	{
		uint16_t abp = BK_BMB11; // вот тут блок параметров
		// получим код для подключения страниц
		uint16_t nPageCode = GetWord(064) & 0204;
		int nWnd0 = GetByte(abp + BK_BMB11_PAGES) & 7;
		int nWnd1 = GetByte(abp + BK_BMB11_PAGES + 1) & 7;
		nPageCode |= (m_arPageCodes[nWnd0] << 12) | (m_arPageCodes[nWnd1] << 8) | 04000;
		uint16_t nSysPageCode = GetWord(BK_BMB11_SYSPG);
		// получим адрес блока параметров из R1 (BK emt 36)
		uint16_t fileAddr = GetWord(abp + BK_BMB10_ADDRESS);  // второе слово - адрес загрузки/сохранения
		uint16_t fileSize = GetWord(abp + BK_BMB10_LENGTH);  // третье слово - длина файла (для загрузки может быть 0)
		uint16_t cs = GetWord(BK_BMB11_CRCADDR);// заберём подсчитанную КС
		bool bError = SaveTapeFunc(abp, nPageCode, nSysPageCode, fileAddr, fileSize, cs);

		if (bError)
		{
			m_pCPU->SetRON(CCPU::REGISTER::PC, 0143752); // выходим на обработку ошибок.
		}
		else
		{
			m_pCPU->SetRON(CCPU::REGISTER::PC, 0144026); // выходим туда.
		}

		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0); // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

	return false;
}

constexpr auto BK_BMB11M = 042602;
constexpr auto BK_BMB11M_ERRADDR = 042657;
constexpr auto BK_BMB11M_RNOFLAG = 042661;
constexpr auto BK_BMB11M_FICTFLAG = 042662;
constexpr auto BK_BMB11M_USRPG = 042666;
constexpr auto BK_BMB11M_CRCADDR = 042672;
constexpr auto BK_BMB11M_FICTADDR = 0164;

// для БК11M.
bool CPlatform::EmulateLoadTape11M()
{
	if (g_Config.m_bEmulateLoadTape && ((GetWord(0154614) == 04767) && (GetWord(0154616) == 0165536)))
	{
		LTF11Param p =
		{
			BK_BMB11M,
			GetWord(BK_BMB11M_USRPG), // получим код для подключения страниц
			054002,                     // код системной страницы БОС
			BK_BMB11M_ERRADDR,
			BK_BMB11M_CRCADDR,
			BK_BMB11M_FICTADDR,
			0155646,
			0154762,
			!!GetByte(BK_BMB11M_RNOFLAG), //прочитать только имя файла
			!!GetByte(BK_BMB11M_FICTFLAG) //фиктивное чтение
		};
		bool bError = LoadTapeFunc11(p);
		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0); // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

	return false;
}

bool CPlatform::EmulateSaveTape11M() const
{
	if (g_Config.m_bEmulateSaveTape && ((GetWord(0154614) == 04767) && (GetWord(0154616) == 0165536)))
	{
		uint16_t abp = BK_BMB11M; // вот тут блок параметров
		// получим код для подключения страниц
		uint16_t nPageCode = GetWord(BK_BMB11M_USRPG);
		uint16_t nSysPageCode = 054002;
		// получим адрес блока параметров из R1 (BK emt 36)
		uint16_t fileAddr = GetWord(abp + BK_BMB10_ADDRESS);  // второе слово - адрес загрузки/сохранения
		uint16_t fileSize = GetWord(abp + BK_BMB10_LENGTH);  // третье слово - длина файла (для загрузки может быть 0)
		uint16_t cs = GetWord(BK_BMB11M_CRCADDR);// заберём подсчитанную КС
		bool bError = SaveTapeFunc(abp, nPageCode, nSysPageCode, fileAddr, fileSize, cs);

		if (bError)
		{
			m_pCPU->SetRON(CCPU::REGISTER::PC, 0154762); // выходим на обработку ошибок.
		}
		else
		{
			m_pCPU->SetRON(CCPU::REGISTER::PC, 0155312); // выходим туда.
		}

		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER, 0); // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

	return false;
}

bool CPlatform::LoadTapeFunc11(LTF11Param p)
{
	bool bFileSelect = false; // что делать после диалога выбора
	bool bCancelSelectFlag = false; // флаг для усложнения алгоритма
	bool bError = false;
	bool bIsDrop = false;
	CString strBinExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
	uint16_t fileAddr = 0;
	fs::path strFileName;
	// Внутренняя загрузка на БК
	uint8_t bkName[BK_NAMELENGTH] = { 0 };  // Максимальная длина имени файла на БК - BK_NAMELENGTH байтов
	uint8_t bkFoundName[BK_NAMELENGTH] = { 0 };

	if (isBinFileNameSet())
	{
		// если загружаем не через драг-н-дроп, то действуем как обычно
		fileAddr = GetWord(p.abp + BK_BMB10_ADDRESS);   // второе слово - адрес загрузки/сохранения

		// Подбираем 16 байтовое имя файла из блока параметров
		for (uint16_t c = 0; c < BK_NAMELENGTH; ++c)
		{
			bkName[c] = GetByte(p.abp + BK_BMB10_NAME + c);
		}

		strFileName = Global::BKToUNICODE(bkName, BK_NAMELENGTH).Trim().GetString(); // тут надо перекодировать  имя файла из кои8 в unicode

		if (!strFileName.empty()) // если имя файла не пустое
		{
			strFileName += strBinExt.GetString(); // добавляем стандартное расширение для бин файлов,
			// чтобы не рушить логику следующих проверок
		}
	}
	else
	{
		// если загружаем через драг-н-дроп, то берём имя оттуда
		strFileName = GetStrBinFileName();;
		bIsDrop = true;
	}

	if (strFileName.empty())
	{
		// Если имя пустое - то покажем диалог выбора файла.
		bFileSelect = false;
l_SelectFile:
		// Запомним текущую директорию
		fs::path strCurDir = fs::current_path();
		fs::current_path(g_Config.m_strBinPath);
		CString strFilterBin(MAKEINTRESOURCE(IDS_FILEFILTER_BIN));
		CFileDialog dlg(TRUE, nullptr, nullptr,
		                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
		                strFilterBin, m_pScreen->GetBackgroundWindow());
		// Зададим начальной директорией директорию с Bin файлами
		dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath.c_str();

		if (dlg.DoModal() == IDCANCEL)
		{
			// Если нажали Отмену, установим ошибку во втором байте блока параметров
			bError = true; // случилась ошибка
		}
		else
		{
			strFileName = dlg.GetPathName().GetString(); // вот выбранный файл
			g_Config.m_strBinPath = strFileName.parent_path();
			// имя файла надо бы как-то поместить в 0352..0372 иначе некоторые глюки наблюдаются
			CString strFound = dlg.GetFileTitle();
			Global::UNICODEtoBK(strFound, bkFoundName, BK_NAMELENGTH, true); // вот из этого массива будем потом помещать

			if (bFileSelect)
			{
				// тут надо проверить тот ли файл нам подсовывают.
				CString strFindEx = Global::BKToUNICODE(bkName, BK_NAMELENGTH); // с расширением
				fs::path p = strFindEx.GetString();
				CString strFind = p.stem().c_str(); // без расширения

				if (!bIsDrop) // только если не дроп. там не с чем сравнивать
				{
					if (strFind.CollateNoCase(strFound) != 0 && strFindEx.CollateNoCase(strFound) != 0)
					{
						int result = g_BKMsgBox.Show(IDS_BK_ERROR_WRONGFILE, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);

						if (result == IDYES)
						{
							goto l_SelectFile;
						}
						else
						{
							bError = true;
							bCancelSelectFlag = true; // не будем ничего делать, сразу выйдем.
						}
					}
				}
			}
		}

		// восстановим текущую директорию
		fs::current_path(strCurDir);
	}
	else    // Если имя не пустое
	{
		// If Saves Default flag is set loading from User directory
		// Else Load from Binary files directory
		fs::path strCurrentPath = g_Config.m_bSavesDefault ? g_Config.m_strSavesPath : g_Config.m_strBinPath;
		CString s = strFileName.c_str();
		Global::SetSafeName(s); // перекодируем небезопасные символы на безопасные
		strFileName = s.GetString();

		// сейчас узнаем, нужно ли нам добавлять расширение .bin, или наоборот, удалять
		if (CString(strFileName.extension().c_str()).CollateNoCase(strBinExt) == 0)
		{
			// у файла уже есть расширение бин
			if (!fs::exists(strCurrentPath / strFileName)) // если нету файла с расширением.
			{
				fs::path str = strFileName.stem(); // удаляем расширение

				if (fs::exists(strCurrentPath / str)) // если есть файл без расширения
				{
					strFileName = str; // оставим файл без расширения
				}
				else
				{
					// нет файла ни с расширением, ни без расширения
					SetByte(p.nErrAddr, 1);
					bError = true;
				}
			}
		}
		else
		{
			// у файла нету расширения бин
			if (!fs::exists(strCurrentPath / strFileName)) // если нету файла без расширения
			{
				fs::path str = strFileName;
				str += strBinExt; // добавляем стандартное расширение для бин файлов.

				if (fs::exists(strCurrentPath / str))  // если есть файл с расширением
				{
					strFileName = str;
				}
				else
				{
					// нет файла ни с расширением, ни без расширения
					SetByte(p.nErrAddr, 1);
					bError = true;
				}
			}
		}

		CString str = strFileName.stem().c_str();
		Global::UNICODEtoBK(str, bkFoundName, BK_NAMELENGTH, true);
		strFileName = strCurrentPath / strFileName;
	}

	if (!bCancelSelectFlag)
	{
		CFile file;
		uint16_t readAddr = 0;
		uint16_t readSize = 0;

		// Загрузим файл, если ошибок не было
		if (!bError && file.Open(strFileName.c_str(), CFile::modeRead))
		{
			file.Read(&readAddr, sizeof(readAddr));   // Первое слово в файле - адрес загрузки
			file.Read(&readSize, sizeof(readSize));   // Второе слово в файле - длина
			// сплошь и рядом встречаются .bin файлы. у которых во втором слове указана длина
			// меньше, чем длина файла - 4. Это другой формат бин, у которого в начале указывается
			// адрес, длина, имя файла[16], массив[длина], КС - контрольная сумма в конце
			uint16_t filesz = (file.GetLength() < 65536) ? static_cast<uint16_t>(file.GetLength()) : 65535;
			bool bIsCRC = false;

			if (readSize == filesz - 4)
			{
				bIsCRC = false;
			}
			else if (readSize == filesz - 6)
			{
				bIsCRC = true;
			}
			else if (readSize == filesz - 22)
			{
				bIsCRC = true;
				file.Read(bkFoundName, BK_NAMELENGTH); // прочитаем оригинальное имя файла
			}
			else
			{
				// всё равно загрузим. Пусть не бин
				file.Seek(0, CFile::SeekPosition::begin);
				readAddr = 0;
				readSize = filesz;
			}

			SetWord(p.abp + BK_BMB11_FOUND_ADDRESS, readAddr);
			SetWord(p.abp + BK_BMB11_FOUND_LENGTH, readSize);

			if (bkFoundName[0])
			{
				// копируем прочитанное имя файла
				for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
				{
					SetByte(p.abp + BK_BMB11_FOUND_NAME + i, bkFoundName[i]);
				}
			}
			else
			{
				// копируем прочитанное имя файла
				for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
				{
					SetByte(p.abp + BK_BMB11_FOUND_NAME + i, bkName[i]);
				}
			}

			if (fileAddr == 0)
			{
				fileAddr = readAddr;
			}

			if (!p.bReadNameOnly)
			{
				SetWord(0177716, p.nPageCode); // подключаем нужные страницы ОЗУ
				DWORD cs = 0; // подсчитаем контрольную сумму

				// Загрузка по адресу fileAddr
				for (int i = readSize; i > 0; i--)
				{
					uint8_t val;
					file.Read(&val, sizeof(val));

					if (p.bFictive)
					{
						SetByte(p.nFictAddr, val);
					}
					else
					{
						SetByte(fileAddr++, val);
					}

					cs += uint16_t(val);

					if (cs & 0xffff0000)
					{
						cs++;
						cs &= 0xffff;
					}
				}

				SetWord(0177716, p.nSysPageCode); // подключаем системные страницы
				uint16_t crc;

				if (bIsCRC && file.Read(&crc, sizeof(crc)) == sizeof(uint16_t))
				{
					if (crc != LOWORD(cs))
					{
						SetByte(p.nErrAddr, 2);
						cs = crc;
					}
				}

				// а иначе, мы не знаем какая должна быть КС. поэтому считаем, что файл априори верный
				file.Close();
				// Заполняем системные ячейки, как это делает emt 36
				uint16_t loadcrc = LOWORD(cs);
				SetWord(p.nCRCAddr, loadcrc); // сохраним контрольную сумму
			}

			m_pCPU->SetRON(CCPU::REGISTER::PC, p.nOkExitAddr); // выходим туда.
		}
		else
		{
			// При ошибке покажем сообщение
			CString strError;
			strError.Format(IDS_CANT_OPEN_FILE_S, strFileName);
			int result = g_BKMsgBox.Show(strError, MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON2);

			switch (result)
			{
				case IDNO:
					// если не хотим останавливаться, то пойдём на диалог, и поищем файл в другом месте.
					bError = false;
					SetByte(p.nErrAddr, 0);
					bFileSelect = true; // включим проверку на неподходящее имя.
					goto l_SelectFile;

				// если отмена - просто выходим с заданным кодом ошибки
				case IDYES:
					// если хотим остановиться - зададим останов.
					BreakCPU();
					[[fallthrough]];

				// если отмена - просто выходим с заданным кодом ошибки
				case IDCANCEL:
					SetByte(p.nErrAddr, 4);
					break;
			}

			m_pCPU->SetRON(CCPU::REGISTER::PC, p.nErrExitAddr); // выходим на обработку ошибок.
		}
	}
	else
	{
		m_pCPU->SetRON(CCPU::REGISTER::PC, p.nErrExitAddr); // выходим на обработку ошибок.
	}

	return bError;
}

// выход: true - произошла ошибка
//        false - не было ошибок
bool CPlatform::SaveTapeFunc(uint16_t abp, uint16_t nPageCode, uint16_t nSysPageCode, uint16_t fileAddr, uint16_t fileSize, uint16_t &cs) const
{
	if (fileSize)
	{
		uint8_t bkName[BK_NAMELENGTH] {};  // Максимальная длина имени файла на БК - BK_NAMELENGTH байтов

		// Подбираем BK_NAMELENGTH байтовое имя файла из блока параметров
		for (uint16_t c = 0; c < BK_NAMELENGTH; ++c)
		{
			bkName[c] = GetByte(abp + BK_BMB10_NAME + c);
		}

		fs::path strFileName = Global::BKToUNICODE(bkName, BK_NAMELENGTH).Trim().GetString(); // тут надо перекодировать  имя файла из кои8 в unicode

		// Если имя пустое
		if (strFileName.empty())
		{
			// Покажем диалог сохранения
			CFileDialog dlg(FALSE, nullptr, nullptr,
			                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
			                nullptr, m_pScreen->GetBackgroundWindow());
			dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath.c_str();

			if (dlg.DoModal() == IDOK)
			{
				// Получим имя
				strFileName = dlg.GetPathName().GetString();
				g_Config.m_strBinPath = strFileName.parent_path();
				CString s = strFileName.filename().c_str();
				Global::UNICODEtoBK(s, bkName, BK_NAMELENGTH, true);
			}
			else
			{
				// Если отмена - выходим с ошибкой
				return true;
			}
		}
		else
		{
			CString strBinExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
			// Если имя не пустое
			CString s = strFileName.c_str();
			Global::SetSafeName(s); // перекодируем небезопасные символы на безопасные
			strFileName = s.GetString();
			strFileName = (g_Config.m_bSavesDefault ? g_Config.m_strSavesPath : g_Config.m_strBinPath) // подставляем соответствующий путь
			              / strFileName;
			strFileName += strBinExt; // добавляем стандартное расширение для бин файлов.
		}

		CFile file;

		// Save file array if no errors
		if (file.Open(strFileName.c_str(), CFile::modeCreate | CFile::modeWrite))
		{
			// Записываем заголовок бин файла
			file.Write(&fileAddr, sizeof(fileAddr)); // слово адреса
			file.Write(&fileSize, sizeof(fileSize)); // слово длины

			if (g_Config.m_bUseLongBinFormat)
			{
				file.Write(bkName, BK_NAMELENGTH); // имя файла
			}

			SetWord(0177716, nPageCode); // подключаем нужные страницы ОЗУ
			DWORD cs1 = 0; // подсчитаем контрольную сумму

			for (int i = fileSize; i > 0; i--)
			{
				uint8_t val = GetByte(fileAddr++);
				file.Write(&val, sizeof(val));
				cs1 += uint16_t(val);

				if (cs1 & 0xffff0000)
				{
					cs1++;
					cs1 &= 0xffff;
				}
			}

			if (!cs)    //для БК10, возвратим из функции вычисленную КС
			{
				cs = LOWORD(cs1);
			}

			SetWord(0177716, nSysPageCode); // подключаем системные страницы

			if (g_Config.m_bUseLongBinFormat)
			{
				file.Write(&cs, sizeof(uint16_t)); // контрольная сумма
			}

			file.Close();
		}
		else
		{
			return true;
		}
	}

	return false;
}

void CPlatform::SetInterseptAddress(uint16_t nBegin, uint16_t nEnd)
{
	// дополнительная проверка на правильную прошивку.
	if (GetWordIndirect(nBegin + 000) == 0010663
	        && GetWordIndirect(nBegin + 002) == 0000050
	        && GetWordIndirect(nBegin + 004) == 0106763
	        && GetWordIndirect(nBegin + 006) == 0000052
	        && GetWordIndirect(nBegin + 010) == 0012700
	        && GetWordIndirect(nBegin + 012) == 0000004
	        && GetWordIndirect(nBegin + 014) == 0011063
	        && GetWordIndirect(nBegin + 016) == 0000046
	        && GetWordIndirect(nBegin + 020) == 0010710
	        && GetWordIndirect(nBegin + 022) == 0062710
	   )
	{
		// всё ок
		m_nFDDCatchAddr = nBegin;
		m_nFDDExitCatchAddr = nEnd;
	}
	else
	{
		// нестандартная прошивка
		m_nFDDCatchAddr = 0177777;
		m_nFDDExitCatchAddr = 0177777;
	}
}


void CPlatform::SetInterseptAddressOptok(uint16_t nBegin, uint16_t nEnd)
{
	m_nFDDCatchAddr = nBegin;
	m_nFDDExitCatchAddr = nEnd;
}

bool CPlatform::EmulateFDD()
{
	// проверим, попали ли на нужный адрес?
	if ((m_pCPU->GetRON(CCPU::REGISTER::PC) & 0177776) != m_nFDDCatchAddr)
	{
		// нет - то и не надо ничего.
		return false;
	}

	CFDDStd326::TABLE_EMFDD dt{};
	uint16_t table_addr = m_pCPU->GetRON(CCPU::REGISTER::R3);
	auto wdt = reinterpret_cast<uint16_t *>(&dt); // структура в виде массива слов.
	uint16_t t = table_addr;

	for (size_t i = 0; i < sizeof(dt) / sizeof(uint16_t); ++i)
	{
		wdt[i] = GetWordIndirect(t);
		t += sizeof(uint16_t);
	}

	int drive = dt.UNIT;

	// Если номер привода выходит за диапазон A: - D:
	if (drive > 3)
	{
		m_pCPU->SetPSWBit(PSW_BIT::C, true);
		SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_0_TRACK_ERROR);
	}
	else
	{
		// эта функция вызывается из экземпляра класса, указатель на который
		// выдаётся по GetFDDPtr(), поэтому он не может быть NULL
		CFloppyDrive *pFDD = GetFDDPtr()->GetFloppyDrive(drive);
		int nSides = 2;

		if (dt.FLGTAB[drive] & 2) // определим, сколько сторон у диска
		{
			nSides = 1;
		}

		// специально для 253й прошивки
		if (0167 != GetWordIndirect(0160016))
		{
			dt.MAXSEC = 10;
		}

		// Если длина  чтения/записи равна 0 - просто выходим без ошибок
		if (!dt.WCNT)
		{
			m_pCPU->SetPSWBit(PSW_BIT::C, false);
			SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_NOERROR);
		}
		else
		{
			// Проверим, примонтирован ли образ в заданный привод

			// Если файл не примонтирован, выходим с ошибкой "Нет Диска"
			if (!pFDD->isAttached())
			{
				m_pCPU->SetPSWBit(PSW_BIT::C, true);
				SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_NO_DISK);
			}
			else
			{
				bool bErrors = false;

				if ((dt.SECTOR == 0 || dt.SECTOR > dt.MAXSEC) || (dt.SIDE >= 2) || (dt.TRK >= 82))
				{
					bErrors = true;
					m_pCPU->SetPSWBit(PSW_BIT::C, true);
					SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_BAD_FORMAT);
				}
				else
				{
					// зададим значение копии регистра по записи как в оригинале
					SetWordIndirect(table_addr, 020 | (1 << drive));
					// высчитаем позицию.
					LONGLONG pos = (((dt.TRK * static_cast<LONGLONG>(nSides)) + dt.SIDE) * dt.MAXSEC + (static_cast<LONGLONG>(dt.SECTOR) - 1)) * 512;

					if (pos == pFDD->FDSeek(pos, CFile::SeekPosition::begin))
					{
						uint16_t addr = dt.ADDR;
						int length = dt.WCNT;
						uint16_t word;
						UINT count;

						if (length > 0)
						{
							// чтение
							do
							{
								count = pFDD->FDRead(&word, sizeof(uint16_t));
								SetWord(addr, word);

								if (count != sizeof(uint16_t))
								{
									m_pCPU->SetPSWBit(PSW_BIT::C, true);
									SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_STOP);
									bErrors = true;
									break;
								}

								addr += sizeof(uint16_t);
							}
							while (--length);
						}
						else
						{
							// Если образ примонтирован, и операция записи, а образ защищён от записи
							if (pFDD->isReadOnly())
							{
								// то сообщим об этом ошибкой
								m_pCPU->SetPSWBit(PSW_BIT::C, true);
								SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_DISK_PROTECTED);
							}
							else
							{
								// запись
								length = -length;

								do
								{
									word = GetWord(addr);

									try
									{
										pFDD->FDWrite(&word, sizeof(uint16_t));
									}
									catch (...) // только так можно отловить ошибку записи
									{
										m_pCPU->SetPSWBit(PSW_BIT::C, true);
										SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_STOP);
										bErrors = true;
										break;
									}

									addr += sizeof(uint16_t);
								}
								while (--length);
							}
						}
					}
					else
					{
						bErrors = true;
						m_pCPU->SetPSWBit(PSW_BIT::C, true);
						SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_BAD_FORMAT);
					}
				}

				if (!bErrors)
				{
					m_pCPU->SetPSWBit(PSW_BIT::C, false);
					SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_NOERROR);
				}
			}
		}
	}

	m_pCPU->SetRON(CCPU::REGISTER::PC, m_nFDDExitCatchAddr);
	return true;
}

bool CPlatform::EmulateFDDOptok()
{
	// проверим, попали ли на нужный адрес?
	if ((m_pCPU->GetRON(CCPU::REGISTER::PC) & 0177776) != m_nFDDCatchAddr)
	{
		// нет - то и не надо ничего.
		return false;
	}

	uint16_t R4 = m_pCPU->GetRON(CCPU::REGISTER::R4); // адрес блока параметров
	int csr = GetWordIndirect(R4 + 0102); //данные регистра управления
	int drive = GetByteIndirect(R4 + 0170) - 1; // 0 - МФ, 1 - привод 0, 2 - привод 1

	if (drive >= 0)
	{
		uint16_t status = 0;
		CFloppyDrive *pFDD = GetFDDPtr()->GetFloppyDrive(drive);

		if (pFDD && pFDD->isAttached())
		{
			int trk = GetWordIndirect(R4 + 0114);
			int sec = GetByteIndirect(R4 + 0122);
			int side = csr & (1 << 11) ? 0 : 1;
			int R2 = GetWordIndirect(R4 + 0144);    // адрес буфера данных
			int R3 = GetWordIndirect(R4 + 0130);    // размер данных в байтах
			bool op = !!GetByteIndirect(R4 + 0126); // true - запись false - чтение
			uint8_t param = GetByteIndirect(R4 + 0171);
			int sides = param & (1 << 3) ? 2 : 1;
			int sectors = param & (1 << 2) ? 5 : 9;

			if (R3) // если есть, что читать/писать - то делаем, иначе -ничего не делаем.
			{
				LONGLONG pos = (((static_cast<LONGLONG>(trk) * sides) + side) * sectors + (static_cast<LONGLONG>(sec) - 1)) * 512;

				if (pos == pFDD->FDSeek(pos, CFile::SeekPosition::begin))
				{
					if (op)
					{
						if (pFDD->isReadOnly())
						{
							status |= (1 << 6);
						}
						else
						{
							// запись
							do
							{
								uint8_t byte = ~GetByte(R2);

								try
								{
									pFDD->FDWrite(&byte, sizeof(uint8_t));
								}
								catch (...)
								{
									status |= (1 << 5);
									break;
								}

								R2 += sizeof(uint8_t);
							}
							while (--R3);
						}
					}
					else
					{
						uint8_t byte;

						// чтение
						do
						{
							UINT count = pFDD->FDRead(&byte, sizeof(uint8_t));
							SetByte(R2, ~byte);

							if (count != sizeof(uint8_t))
							{
								status |= (1 << 4);
								break;
							}

							R2 += sizeof(uint8_t);
						}
						while (--R3);
					}
				}
			}
		}
		else
		{
			status |= (1 << 7);
		}

		if (status)
		{
			uint16_t err = GetWordIndirect(R4 + 0164) | 4;
			SetWordIndirect(R4 + 0164, err);
			status |= (1 << 3);
		}

		SetWordIndirect(R4 + 0106, ~status);
		SetWordIndirect(R4 + 0104, status);
	}

	m_pCPU->SetRON(CCPU::REGISTER::PC, m_nFDDExitCatchAddr);
	return true;
}

void CPlatform::StopTimerThread()
{
	if (!m_mutLockTimerThread.try_lock())
	{
		m_bKillTimerEvent = true;
		StopCPU();
		MSG msg;
		BOOL bRet;

		while (!m_mutLockTimerThread.try_lock()) // ждём завершения потока
		{
			// использовать lock_guard или просто lock нельзя, потому что это
			// остановит очередь сообщений и UI полностью зависнет в дедлоке
			// Хотя оно как-то умудляется всё равно остановить очередь сообщений,
			// поэтому берём на себя трансляцию сообщений, чтобы UI не зависало
			// этот костыль всё-таки нужен. Без него при загрузке сохранения получается дедлок UI.
			if (bRet = GetMessage(&msg, m_pMainFrame->GetSafeHwnd(), 0, 0))
			{
				if (bRet != -1)
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
		}

		// поток остановился, и можно продолжать остальные действия по остановке
	}

	m_mutLockTimerThread.unlock();
}

bool CPlatform::StartTimerThread()
{
	m_TimerThread = std::thread(&CPlatform::TimerThreadFunc, this);
	m_bKillTimerEvent = false;

	if (m_TimerThread.joinable())
	{
		m_TimerThread.detach();
		return true;
	}

	g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	return false;
}

void CPlatform::FrameParam()
{
	if (m_nCPUFreq_prev != m_nCPUFreq) // если частота не менялась, пересчёта не делать
	{
		m_nCPUFreq_prev = m_nCPUFreq;
		const auto fCPUfreq = double(m_nCPUFreq);
		m_fCpuTickTime = 1.0 / fCPUfreq; // длительность одного такта в секундах
		m_fMemory_Mod = fCPUfreq / 750000.0; // коэффициент пересчёта ТЧ проца в количество обращений к видеопамяти за 1c
		m_fFDD_Mod = (fCPUfreq / 15625.0);  // 15625 = 5 оборотов в сек, в одном обороте 3125 слов,

		m_fAZVideo_mod = 0.000020676923076923 / m_fCpuTickTime;

		// норма 15625.0, больше - быстрее вращение, меньше - медленнее
		if (GetFDDPtr() && GetFDDPtr()->GetBoardModel() == BK_DEV_MPI::OPTOK)
		{
			m_fFDD_Mod /= 2; // увеличим количество вызовов в единицу времени, там побайтный обмен, поэтому надо в два раза больше обращений
		}

		m_fMedia_Mod = fCPUfreq / double(g_Config.m_nSoundSampleRate); // коэффициент пересчёта тактов процессора в медиа такты
		// означает - через сколько тактов процессора надо вставлять 1 медиа такт, т.к. число не целое, то
		// получается приблизительно.
	}
}


void CPlatform::TimerThreadFunc() const
{
	std::lock_guard<std::mutex> lk(m_mutLockTimerThread);
	//auto pBoard = GetBoardPtr();
	//auto pFDD = GetFDDPtr();
	uint16_t  nCurrentPC = BREAK_ADDRESS::ADDRESS_NONE; // значение регистра РС после выполнения очередной инструкции
	// типы nCurrentPC и m_nGotoAddress не должны совпадать, иначе будет всегда срабатывать условие отладочного
	// останова, даже если нам этого не надо

	do
	{
		if (m_bRunning.IsLocked()) // если процессор работает, выполняем эмуляцию
		{
			m_mutRunLock.lock(); // блокируем участок, чтобы при остановке обязательно дождаться, пока фрейм не закончится

			if (m_bBreaked.IsLocked()) // если процессор в отладочном останове
			{
				DrawDebugScreen();  // продолжаем обновлять экран
				::Sleep(20);        // со стандартной частотой примерно 50Гц
			}
			else
			{
				// Выполняем набор инструкций
				// Если время пришло, выполняем текущую инструкцию
				if (--m_nCPUTicks <= 0)
				{
					try
					{
						// если возможен и был перехват п/п монитора
						// или если включена эмуляция в/в дисковода и возможен и был перехват
						if ((m_pBoard && m_pBoard->Interception(nCurrentPC)) || (g_Config.m_bEmulateFDDIO && m_pFDD && m_pFDD->Interception()))
						{
							m_nCPUTicks = 8; // то, будет вот так
						}
						else // иначе:
						{
							// Выполняем одну инструкцию, возвращаем время выполнения одной инструкции в тактах.
							m_nCPUTicks = m_pCPU->TranslateInstruction();
						}

						// Сохраняем текущее значение PC для отладки
						nCurrentPC = m_pCPU->GetRON(CCPU::REGISTER::PC);
					}
					catch (CExceptionHalt &halt)
					{
						// BK Violation exception. Например запись в ПЗУ или чтение из несуществующей памяти
						if (g_Config.m_bAskForBreak)
						{
							BreakCPU(); // Останавливаем CPU для отладки
							CString strMessage;
							strMessage.Format(IDS_ERRMSG_ROM, halt.m_addr, nCurrentPC, halt.m_info);
							const int answer = g_BKMsgBox.Show(strMessage, MB_ABORTRETRYIGNORE | MB_DEFBUTTON2 | MB_ICONSTOP);

							switch (answer)
							{
								case IDIGNORE:  // если выбрали "игнорировать"
									g_Config.m_bAskForBreak = false; // то снимаем галку, и больше не вызываем диалог
									[[fallthrough]];
								// и выполним всё, что должно выполняться для "повтор"
								case IDRETRY: // если выбрали "повтор" - то просто продолжить выполнение и посмотреть, что будет дальше
									UnbreakCPU(BREAK_ADDRESS::ADDRESS_NONE); // обратно запускаем CPU
									m_pCPU->ReplyError(); // Делаем прер. по вектору 4(halt) в следующем цикле
									break;

								case IDABORT: // если выбрали "прервать" - остаёмся в отладочном останове
								default:
									break;
							}
						}
						else
						{
							m_nCPUTicks = 64;
							m_pCPU->ReplyError(); // Делаем прер. по вектору 4(halt) в следующем цикле
						}

						nCurrentPC = BREAK_ADDRESS::ADDRESS_NONE;
					}
					catch (...)
					{
						// Любое другое исключение. Неизвестное исключение или ошибка доступа к памяти WINDOWS.
						// Эта ошибка может быть неизвестной ошибкой BK или ошибкой эмулятора
						CString strMessage = MAKEINTRESOURCE(IDS_ERRMSG_INTERNAL);
						g_BKMsgBox.Show(strMessage, MB_OK | MB_ICONSTOP);
						BreakCPU();
						m_mutRunLock.unlock();
						StopCPU(false);
						break; // останавливаем поток
					}

					const bool c1 = m_pDebugger->IsBreakedPoints();	// Спросим отладчик на счёт условий остановки. Если ВОИСТИНУ останов
					const bool c2 = (m_nGotoAddress == nCurrentPC);			// Если останов на адресе где стоит отладочный курсор
					const bool c3 = (m_nGotoAddress == CPlatform::BREAK_ADDRESS::GO_INTO);	// Отладочный останов если только одиночный шаг
					const bool c4 = (m_nGotoAddress == CPlatform::BREAK_ADDRESS::GO_OUT && CDebugger::IsInstructionOut(m_pCPU->GetRunnedInstruction())); // Отладочный останов если команда выхода из п/п
					if ( c1 || c2 || c3 || c4 )
					{
						BreakCPU();
					}
				}

				if (m_pFDD)
				{
					if (--m_fFDDTicks <= 0.0)
					{
						do
						{
							m_pFDD->Periodic();    // Вращаем диск на одно слово на дорожке
							m_fFDDTicks += m_fFDD_Mod;
						}
						while (m_fFDDTicks < 1.0);
					}
				}

				if (--m_fMemoryTicks <= 0.0)
				{
					do
					{
						Make_One_Screen_Cycle(); // тут выполняются циклы экрана
						m_fMemoryTicks += m_fMemory_Mod;
					}
					while (m_fMemoryTicks < 1.0);
				}

				if (m_pAZBK)
				{
					if (--m_fAZVideoTicks <= 0.0)
					{
						do
						{
							m_pAZBK->MakeScreenLine(); // Выполняем одну линию ВГА
							m_fAZVideoTicks += m_fAZVideo_mod;
						} while (m_fAZVideoTicks < 1.0);
					}
				}

				m_pSpeaker->RCFilterLF(m_fCpuTickTime); // эмуляция конденсатора на выходе линейного входа.

				if (--m_fMediaTicks <= 0.0)
				{
					do
					{
						MediaTick(); // тут делается звучание всех устройств и обработка прочих устройств
						m_fMediaTicks += m_fMedia_Mod;
					}
					while (m_fMediaTicks < 1.0);
				}
			}
			m_mutRunLock.unlock();
		}
		else // если процессор не работает, небольшая пауза
		{
			Sleep(20);
		}
	}
	while (!m_bKillTimerEvent);     // пока не придёт событие остановки

	m_bKillTimerEvent = false;
}


// специально для отладки, рисуем экран старым методом.
// при этом новый метод не отключается и поэтому возможны разного рода казусы.
void CPlatform::DrawDebugScreen() const
{
	m_pMainFrame->PostMessage(WM_SCR_DEBUGDRAW);
}

// это вариант точного алгоритма из VP1-037
void CPlatform::Make_One_Screen_Cycle() const
{
	int dww = m_sOSCV.nVideoAddress & 076; // счётчик слов внутри строки
	int dwa = m_sOSCV.nVideoAddress & 037700; // адрес строки

	if (!(m_sOSCV.nVGateCounter || m_sOSCV.bHgate))
	{
		m_pScreen->SetExtendedMode(!(m_pBoard->m_reg177664 & 01000));
		const DWORD_PTR nScrAddr = (static_cast<DWORD_PTR>(m_pBoard->GetScreenPage())) << 14;
		const uint8_t *nScr = m_pBoard->GetMainMemory() + nScrAddr + m_sOSCV.nVideoAddress;
		m_pScreen->PrepareScreenLineWordRGB32(m_sOSCV.nLineCounter, dww, *reinterpret_cast<const uint16_t *>(nScr));
	}

	dww += 2; // переходим к следующему слову

	if (m_sOSCV.bHgate) // считаем служебное поле в строке
	{
		if (dww == 040) // отсчитали 16. служебных слов
		{
			dww = 0;
			m_sOSCV.bHgate = false;
		}
	}
	else if (dww == 0100)
	{
		// если отсчитали 32. слова, нужно теперь считать служебные
		dww = 0;
		m_sOSCV.bHgate = true;
		int dwl = (m_sOSCV.nLineCounter + 1) & 0377; // Счётчик строк

		if (m_sOSCV.nVGateCounter) // считаем служебные
		{
			if (--m_sOSCV.nVGateCounter <= 0) // отсчитали 64 служебные строки
			{
				dwl = 0;  // снова начнём считать информационные
			}

			// когда считаем служебные строки, выбираем из них 353..350
			if ((m_sOSCV.nVGateCounter & 0374) == 050)    // if (050 <= m_sOSCV.nVGateCounter && m_sOSCV.nVGateCounter <= 053)
			{
				// если строки 351, 350
				if (!(m_sOSCV.nVGateCounter & 2))         // if (050 == m_sOSCV.nVGateCounter || m_sOSCV.nVGateCounter == 051)
				{
					// применяем новое смещение
					dwa = (m_pBoard->m_reg177664 & 0377) << 6; // VA[13:6] <= RA[7:0];
				}

				// при этом на новую строку не переходим. в одной крутимся.
			}
			else
			{
				// переход на следующую строку
				dwa += 0100; // VA[13:6] <= VA[13:6] + 8'b00000001;
			}
		}
		else
		{
			if (dwl == 0)
			{
				// если отсчитали 256 информационных строк, нужно теперь считать служебные.
				m_sOSCV.nVGateCounter = 64;

				if (!(m_pBoard->m_reg177662out & 040000)) // если бит 14 установлен, таймер не работает.
				{
					GetCPUPtr()->TickIRQ2();
					// для лога дампа регистров AY
					GetAYSndPtr()->log_timerTick();
				}

				// или таймер 50Гц от AZBK (пока будем делать так, для простоты)
				if (m_pAZBK && m_pAZBK->Clk50НzFunc())
				{
					GetCPUPtr()->TickIRQ2();
				}

				//m_pMainFrame->PostMessage(WM_SCR_DRAW, 0); // так медленнее, но всё корректно, и D2D не тормозит, когда окно свёрнуто.
				m_pScreen->DrawScreen(true, 0); // рисуем напрямую. Так быстрее, но в Direct2D тормозит, когда окно свёрнуто.
				// но никак не могу вспомнить, почему я через m_pMainFrame выбрал рисование.
				// может дедлоки возникали иногда?
			}

			// переход на следующую строку
			dwa += 0100; // VA[13:6] <= VA[13:6] + 8'b00000001;
		}

		m_sOSCV.nLineCounter = dwl;
	}

	m_sOSCV.nVideoAddress = (dwa | dww) & 037776;
}


void CPlatform::MediaTick() const
{
	// обработка медиатактов
	if (m_sMTV.nMediaTickCount == 0)
	{
		// Получаем сохранённые 1/CPU_FRAMERATE секунды звуковые данные
		// Берём данные с ленты -> m_pSoundBuffer, а если нечего брать, очищаем буфер
		m_pTape->PlayWaveGetBuffer(m_sMTV.pSoundBuffer.get(), m_sMTV.nMediaTicksPerFrame);
		// Посылаем данные с ленты в 177716 <- m_pSoundBuffer
		m_pSpeaker->ReceiveTapeBuffer(m_sMTV.pSoundBuffer.get(), m_sMTV.nMediaTicksPerFrame);
		m_sMTV.pBufPos = m_sMTV.pSoundBuffer.get();
	}

	// осуществляем приём данных с магнитофона, так он будет работать даже если спикер выключен
	if (m_pSpeaker->GetTapeSample())
	{
		m_pBoard->m_reg177716in |= 040;
	}
	else
	{
		m_pBoard->m_reg177716in &= ~040;
	}

	sOneSample sm{ 0.0, 0.0 };
	m_pSound->SoundGen_ResetSample(*(m_sMTV.pBufPos), *(m_sMTV.pBufPos + 1)); // задаём начальные уровни для микширования

	// Берём звуки со спикера БК
	if (m_pSpeaker->IsSoundEnabled())
	{
		m_pSpeaker->GetSample(&sm);
		m_pSound->SoundGen_MixSample(&sm); // микшируем сэмплы внутри m_Sound
	}

	// Берём данные с ковокса
	if (m_pCovox->IsSoundEnabled())
	{
		m_pCovox->GetSample(&sm);
		m_pSound->SoundGen_MixSample(&sm);
	}

	if (m_pMenestrel->IsSoundEnabled())
	{
		m_pMenestrel->GetSample(&sm);
		m_pSound->SoundGen_MixSample(&sm);
	}

	// Берём данные с ay8910
	if (m_pAYSnd->IsSoundEnabled())
	{
		m_pAYSnd->GetSample1(&sm);
		m_pSound->SoundGen_MixSample(&sm);

		if (g_Config.m_b2ndAY8910)
		{
			m_pAYSnd->GetSample2(&sm);
			m_pSound->SoundGen_MixSample(&sm);
		}
	}

	m_pSound->SoundGen_FeedDAC_Mixer(&sm); // получаем сюда смикшированное, а там - осуществляем звучание
	*m_sMTV.pBufPos++ = sm.s[OSL]; // и помещаем в m_pSoundBuffer, это нужно для осциллографа
	*m_sMTV.pBufPos++ = sm.s[OSR]; // и для сохранения результата звучания в wav

	if (++m_sMTV.nMediaTickCount >= m_sMTV.nMediaTicksPerFrame)
	{
		m_sMTV.nMediaTickCount = 0;
		// Отображаем буфер на осциллографе
		// сперва синхронно копируем текущий буфер в осциллограф, чтобы он его там отображал
		m_pMainFrame->SendMessage(WM_OSC_FILLBUFFER, 0, reinterpret_cast<LPARAM>(m_sMTV.pSoundBuffer.get()));
		// и посылаем его асинхронно отображаться
		m_pMainFrame->PostMessage(WM_OSC_DRAW);
		// Отправляем их на ленту <- m_pSoundBuffer
		m_pTape->RecordWaveGetBuffer(m_sMTV.pSoundBuffer.get(), m_sMTV.nMediaTicksPerFrame);
	}
}


