// Platform.cpp: файл реализации
//

#include "pch.h"
#include "BK.h"
#include "Platform.h"

#include "BKMessageBox.h"
#include "ExceptionHalt.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"


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

// CPlatform
CPlatform g_Platform;

CPlatform::CPlatform()
	: m_pMainFrame(nullptr)
	, m_pScript(nullptr)
	, m_pCPU(nullptr)
	, m_pDebugger(nullptr)
	, m_nGotoAddress(ADDRESS_NONE)
	, m_nLowBound(1)
	, m_nHighBound(100000000)
	, m_nFDDCatchAddr(-1)
	, m_nFDDExitCatchAddr(-1)
	, m_bBreaked(false)
	, m_bRunning(false)
	, m_bKillTimerEvent(false)
	, m_nCPUFreq_prev(-1)
{
	m_pScript = new CScriptRunner();
	m_pCPU = new CCPU();
	m_pDebugger = new CDebugger();
	m_pMediaProc = new CMediaProc();
	m_pVideoProc = new CVideoProc();
	m_pPeriodicProc = new CPeriodicProc();

	if (!(m_pScript && m_pCPU && m_pDebugger && m_pMediaProc && m_pVideoProc && m_pPeriodicProc))
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		throw; // всё ломаем. и отменяем
	}

	ClearDevicesVector();
	m_nCPUTicks = 0;
	m_pVideoProc->init();
	m_pMediaProc->init();
	m_pPeriodicProc->init();
}

CPlatform::~CPlatform()
{
	ClearDevicesVector();
	m_pVideoProc->uninit();
	m_pMediaProc->uninit();
	m_pPeriodicProc->uninit();
	SAFE_DELETE(m_pVideoProc);
	SAFE_DELETE(m_pMediaProc);
	SAFE_DELETE(m_pPeriodicProc);
	SAFE_DELETE(m_pDebugger);
	SAFE_DELETE(m_pCPU);
	SAFE_DELETE(m_pScript);
}

void CPlatform::ClearDevicesFounds()
{
	m_bBoardFound = false;
	m_pBoard = nullptr;
	m_bMSTDFound = false;
	m_pMSTD = nullptr;
	m_bFDDFound = false;
	m_pFDD = nullptr;
	m_bIRPSFound = false;
	m_pIRPS = nullptr;
}

void CPlatform::ClearDevicesVector()
{
	ClearDevicesFounds();

	for (auto &p : m_pMPIDevices)
	{
		SAFE_DELETE(p);
	}

	m_pMPIDevices.clear();
}

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

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

		m_bBoardFound = true;
	}

	return m_pBoard;
}

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

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

		m_bFDDFound = true;
	}

	return m_pFDD;
}

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

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

		m_bMSTDFound = true;
	}

	return m_pMSTD;
}

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

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

		m_bIRPSFound = true;
	}

	return m_pIRPS;
}

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

void CPlatform::InitEmulator()
{
	m_pVideoProc->InitEmulator();
	m_pMediaProc->InitEmulator();
}

// выносим это в отдельную процедуру, т.к. есть вероятность неудачного прочтения MSF,
// при этом надо восстановить всё как было раньше
void CPlatform::InitObjects()
{
	int nMtc = m_pMediaProc->InitObjects();
	// установка размера буфера для осциллографа.
	m_pMainFrame->SendMessage(WM_OSC_SETBUFFER, WPARAM(nMtc));

	// если в ини файле задана частота, то применим её, вместо частоты по умолчанию.
	if (g_Config.m_nCPUFrequency)
	{
		SetCPUFreq(g_Config.m_nCPUFrequency);
	}
}


bool CPlatform::StartPlayTape()
{
	return m_pMediaProc->StartPlayTape(g_Config.m_strTapePath + m_strTapeFileName);
}

bool CPlatform::StartPlayTape(const CString &strPath)
{
	return m_pMediaProc->StartPlayTape(strPath);
}

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

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

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

	if (!pBoard)
	{
		return;
	}

	if (IsCPURun()) // защита от множественного вызова функции.
	{
		StopCPU();
		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)
		)
		{
			auto a16m = dynamic_cast<CFDD_A16M *>(GetFDDPtr());
			ASSERT(a16m);
			a16m->SetAltProMode(ALTPRO_A16M_START_MODE);
			a16m->SetAltProCode(0);
			pBoard->ResetCold(0);
		}
		else
		{
			// если у нас БК11, то можно реализовать СУ/ресет - перезапуск по 100000
			if ((pBoard->GetBoardModel() != BK_DEV_MPI::BK001001) && bIsSuPress)
			{
				pBoard->ResetCold(040000);
			}
			else
			{
				pBoard->ResetCold(0);
			}

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

		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_pVideoProc->clear();
	m_pMediaProc->clear();
	m_pPeriodicProc->clear();
	m_nCPUFreq_prev = 0;
	FrameParam();
	m_pCPU->InitCPU();
	// ещё косяк - в функции помимо инициализации делается ресет устройства.
	// а в функции InitCPU вызывается ResetPlatform, который делает только ресет устройства
	// получается дублирование ресетов, лишнее действие.
	// хоть это не критично, и эти функции делались немного для разных целей, ресет нужен
	// и там, и там, но вот вместе они дают такой эффект.
	return true;
}

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

	m_pMainFrame->PostMessage(WM_RESET_KBD_MANAGER);    // тут делается инициализация обработчика клавиатуры
}


void CPlatform::ClearProcessingFiles()
{
	m_strBinFileName.Empty();
	m_strMemFileName.Empty();
	m_strTapeFileName.Empty();
	m_strScriptFileName.Empty();
}

bool CPlatform::DropFiles(CString *strPath)
{
	ClearProcessingFiles();
	CString strFilePath = ::GetFilePath(*strPath);
	CString strFileExt = ::GetFileExt(*strPath);
	CString strBinExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
	CString strMemExt(MAKEINTRESOURCE(IDS_FILEEXT_MEMORYSTATE));
	CString strTapExt(MAKEINTRESOURCE(IDS_FILEEXT_TAPE));
	CString strWavExt(MAKEINTRESOURCE(IDS_FILEEXT_WAVE));
	CString strScptExt(MAKEINTRESOURCE(IDS_FILEEXT_SCRIPT));

	if (!strFileExt.CompareNoCase(strBinExt))  // Binary file
	{
		m_strBinFileName = ::GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strBinPath = strFilePath;
		}

		m_strScriptFileName = _T("autorun\\monitor_load.bkscript");
		m_pScript->SetArgument(::GetFileTitle(m_strBinFileName));
	}
	else if (!strFileExt.CompareNoCase(strMemExt))  // Memory state file
	{
		m_strMemFileName = ::GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strMemPath = strFilePath;
		}
	}
	else if (strFileExt.CompareNoCase(strTapExt) == 0
	         || strFileExt.CompareNoCase(strWavExt) == 0)  // BK tape file
	{
		m_strTapeFileName = ::GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strTapePath = strFilePath;
		}
	}
	else if (!strFileExt.CompareNoCase(strScptExt))  // Initial Script file
	{
		m_strScriptFileName = ::GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strScriptsPath = strFilePath;
		}
	}
	else
	{
		ASSERT(false);
		return false;
	}

	return true;
}

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

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

	// если ключи есть, оставляем только их.
	strCommands = strCommands.Right(strCommands.GetLength() - max(0, nFindPos));
	int nStrLen; // текущая длина оставшейся неразобранной строки

	while (nStrLen = strCommands.GetLength())
	{
		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(); // снова уберём пробелы. теперь они точно есть
		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 true;
				}

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

	return true;
}

bool CPlatform::SetCmdLParameters(TCHAR command, CString &strParam)
{
	CString strFilePath = ::GetFilePath(strParam);

	switch (command)
	{
		case _T('B'): // Binary file
			m_strBinFileName = ::GetFileName(strParam);

			if (!strFilePath.IsEmpty())
			{
				g_Config.m_strBinPath = strFilePath;
			}

			m_strScriptFileName = _T("autorun\\monitor_load.bkscript");
			m_pScript->SetArgument(::GetFileTitle(m_strBinFileName));

			break;

		case _T('M'): // Memory state file
			m_strMemFileName = ::GetFileName(strParam);

			if (!strFilePath.IsEmpty())
			{
				g_Config.m_strMemPath = strFilePath;
			}

			break;

		case _T('T'): // BK tape file
			m_strTapeFileName = ::GetFileName(strParam);

			if (!strFilePath.IsEmpty())
			{
				g_Config.m_strTapePath = strFilePath;
			}

			break;

		case _T('S'): // Initial Script file
			m_strScriptFileName = ::GetFileName(strParam);

			if (!strFilePath.IsEmpty())
			{
				g_Config.m_strScriptsPath = strFilePath;
			}

			break;

		default:
			return false;
	}

	return true;
}


void CPlatform::SetScriptPath(bool bXlat)
{
	m_pScript->SetScriptPath(g_Config.m_strScriptsPath, m_strScriptFileName, bXlat);
}

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

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


CBKMPIDevice *CPlatform::CreateDevice(BK_DEV_MPI device)
{
	CBKMPIDevice *pDev = nullptr;

	switch (device)
	{
		case BK_DEV_MPI::BK001001:
			pDev = new CBK001001();
			break;

		case BK_DEV_MPI::BK0011:
			pDev = new CBK0011();
			break;

		case BK_DEV_MPI::BK0011M:
			pDev = new CBK0011M();
			break;

		case BK_DEV_MPI::NONE:
			break;

		case BK_DEV_MPI::MSTD10:
			pDev = new CBKMSTD10();
			break;

		case BK_DEV_MPI::MSTD10_EXT32:
			pDev = new CBKMSTD10_EXT32();
			break;

		case BK_DEV_MPI::MSTD11:
			pDev = new CBKMSTD11();
			break;

		case BK_DEV_MPI::MSTD11M:
			pDev = new CBKMSTD11M();
			break;

		case BK_DEV_MPI::STD_326:
			pDev = new CFDDStd326();
			break;

		case BK_DEV_MPI::STD_253:
			pDev = new CFDDStd253();
			break;

		case BK_DEV_MPI::STD_MEM:
			pDev = new CFDDStd326Mem();
			break;

		case BK_DEV_MPI::A16M:
			pDev = new CFDD_A16M();
			break;

		case BK_DEV_MPI::SMK512:
			pDev = new CFDD_SMK512();
			break;

		case BK_DEV_MPI::SAMARA:
			pDev = new CFDD_Samara();
			break;

		default:
			ASSERT(false);
	}

	return pDev;
}

bool CPlatform::CreateBoard(CONF_BKMODEL nConf)
{
	g_Config.SetBKModelNumber(nConf);
	// создадим новую конфигурацию
	auto pDev = CreateDevice(g_Config.GetBKBoardModel());

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

		if (pDev)
		{
			m_pMPIDevices.push_back(pDev);
		}

		// теперь создадим блок ИРПС
		pDev = new CIRPS();

		if (pDev)
		{
			m_pMPIDevices.push_back(pDev);
			return true;
		}
	}

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

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

	ClearDevicesVector();
}

void CPlatform::RunBoard()
{
	StartTimerThread();
	m_pMainFrame->PostMessage(WM_START_MAINLOOPTIMER);
	// Запускаем CPU
	RunCPU();

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


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

	if (m_pDebugger->IsInstructionOver(instr))
	{
		RunToAddr(pc + m_pDebugger->CalcInstructionLength(instr));
	}
	else if ((NextAddr = m_pDebugger->CalcNextAddr(pc)) != ADDRESS_NONE)
	{
		RunToAddr(NextAddr);
	}
	else
	{
		RunInto();
	}
}

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


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


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


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

void CPlatform::UnbreakCPU(int nGoto)
{
	m_nGotoAddress = nGoto;
	m_bBreaked = false; // отменяем отладочную приостановку
}


inline bool CPlatform::IsCPUBreaked()
{
	return m_bBreaked;
}

void CPlatform::RunCPU(bool bUnbreak)
{
	if (bUnbreak)
	{
		UnbreakCPU(ADDRESS_NONE);
	}

	// Set running flag
	m_bRunning = true;
}


void CPlatform::StopCPU(bool bUnbreak)
{
	m_bRunning = false;

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

	Sleep(40); // !!!костыль, т.к. нету никакой синхронизации, после останова надо убедиться, что цикл в потоке
	// воспринял новое значение флага и не выполняет инструкций
}


inline bool CPlatform::IsCPURun()
{
	return m_bRunning;
}


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

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

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

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

	if (res)
	{
		return b2;
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't read this address."));
	}

	return 0;
}

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

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

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

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

	if (res)
	{
		return w2;
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't read this address."));
	}

	return 0;
}

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

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

	for (auto &p : m_pMPIDevices)
	{
		if (p)
		{
			register int ntc;
			register 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(uint16_t addr, uint16_t w)
{
	int nTC = 0;
	SetWordT(addr, w, nTC);
}

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

	for (auto &p : m_pMPIDevices)
	{
		if (p)
		{
			register int ntc;
			register 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(uint16_t addr)
{
	if (0177700 <= addr && addr < 0177714)
	{
		register uint16_t w = m_pCPU->GetSysRegs(addr);
		return (addr & 1) ? HIBYTE(w) : LOBYTE(w);
	}

	register uint8_t b2 = 0;

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

	return b2;
}

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

	register uint16_t w2 = 0;

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

	return w2;
}

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

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


void CPlatform::KeyboardInterrupt(uint16_t interrupt)
{
	if (!(GetBoardPtr()->m_reg177660 & 0100))
	{
		// если прерывания разрешены, делаем прерывание
		m_pCPU->InterruptVIRQ(interrupt);
	}
}

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

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

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

	if (::GetAsyncKeyState(VK_SHIFT))
	{
		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)
	{
		register int n = CalcStep(); // если мегакомбо, для увеличения - увеличиваем в 2 раза

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

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

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

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

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

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

bool CPlatform::CanAccelerate()
{
	return !(m_nCPUFreq >= m_nHighBound);
}

bool CPlatform::CanSlowDown()
{
	return !(m_nCPUFreq <= m_nLowBound);
}

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

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

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

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

	m_nCPUFreq = frq;
}

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

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


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

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

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

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

			if (InitPlatform())
			{
				if (RestoreState(pMsf)) // там заново прочитается RestorePlatform и всё пойдёт по задуманному
				{
					// если всё удачно, вот только тогда и уничтожаем старую конфигурацию
					for (auto &p : pOldMPIDevices)
					{
						SAFE_DELETE(p);
					}

					pOldMPIDevices.clear();
					bRet = true;
				}
			}
		}
		else
		{
			g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		}
	}

	if (pPD) // это больше не нужно. удаляем
	{
		SAFE_DELETE_ARRAY(pPD->pDevices);
		delete pPD;
	}

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

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

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

	return bRet;
}

// сохранение состояния эмулятора
bool CPlatform::SaveMemoryState(CString &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
	MSF_PLATFORM_DATA *pPD = nullptr;

	if (RestorePlatform(pMsf, &pPD)) // в любом случае, заполняем структуру MSF_PLATFORM_DATA
	{
		// 2. сохраняем превьюшку - скриншот экрана
		if (RestorePreview(pMsf))
		{
			// 3. сохраняем конфиг
			if (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 = bRet && p->RestoreMemoryMap(id, pMsf); // необходимо делать строго перед работой с памятью.

							if (!bRet)
							{
								break;
							}

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

							if (!bRet)
							{
								break;
							}

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

							if (!bRet)
							{
								break;
							}

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

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

	if (pPD) // это больше не нужно. удаляем
	{
		SAFE_DELETE_ARRAY(pPD->pDevices);
	}

	SAFE_DELETE(pPD);
	return bRet;
}

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

	if (*pPD)
	{
		if (pMsf->IsLoad())
		{
			// загрузка
			return pMsf->GetBlockPlatformData(*pPD);
		}
		else
		{
			// сохранение
			// заполняем структуру, она переменного размера
			(*pPD)->nPlatformConfig = static_cast<DWORD>(g_Config.GetBKModelNumber());
			auto t = static_cast<DWORD>(m_pMPIDevices.size()); // размер вектора
			(*pPD)->nDevNum = t; // пока сохраним результат
			(*pPD)->pDevices = nullptr;
			int n = 0; // счётчики реально существующих устройств в векторе

			if (t) // если в векторе что-то есть
			{
				(*pPD)->pDevices = new MSF_DEVICE_ID[t]; // выделяем память

				if ((*pPD)->pDevices) // если выделилось
				{
					memset((*pPD)->pDevices, 0, sizeof(MSF_DEVICE_ID) * t); // обнуляем
					DWORD nUniqueID = 0x100;

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

						// если вектор будет содержать null вместо указателя на устройство,
						// то надо будет скорректировать кол-во устройств
					}
				}
				else
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
					return false;
				}
			}

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

	return false;
}

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

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

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

			if (hBm)
			{
				bRet = pMsf->SetBlockPreview(hBm);
				DeleteObject(hBm);
			}

			DeleteObject(hScreenshot);
		}
	}

	return bRet;
}

bool CPlatform::RestoreConfig(CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		pMsf->GetBlockConfig();
		MSF_FRAMEDATA framedata;

		if (pMsf->GetBlockFrameData(&framedata))
		{
			m_nCPUTicks = framedata.nCPUTicks;
			m_pMediaProc->SetTicks(framedata.fMediaTicks);
			m_pVideoProc->SetTicks(framedata.fMemoryTicks);
			m_pPeriodicProc->SetTicks(framedata.fFDDTicks);
			
			// вот это обязательно нужно сделать.
			// а то переинициализация фрейма делается опять не в то время, когда надо
			// с ещё старыми параметрами, а потом, с новыми уже не делается.
			m_nCPUFreq_prev = 0;
			FrameParam();

			m_pCPU->SetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
			CVideoProc::ThrOSCVars_t OSCV;
			OSCV.nVideoAddress = framedata.nVideoAddress & 0xffff; // видео адрес, младшие 6 бит - счётчик строк внутри строки
			OSCV.bHgate = (framedata.bHgate != 0); // флаг отсчёта служебных видеоциклов в строке
			OSCV.bVgate = (framedata.bVgate != 0); // флаг отсчёта служебных строк
			OSCV.nVGateCounter = framedata.nVGateCounter; // дополнительный счётчик служебных строк
			OSCV.nLineCounter = framedata.nLineCounter & 0377; // счётчик видео строк
			m_pVideoProc->SetOSCVars(&OSCV);
		}
		else
		{
			m_nCPUTicks = 0;
			m_pMediaProc->clear();
			m_pVideoProc->clear();
			m_pPeriodicProc->clear();
		}

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

	bool bRet = pMsf->SetBlockConfig();
	MSF_FRAMEDATA framedata;
	framedata.nCPUTicks = m_nCPUTicks;
	framedata.fMediaTicks = m_pMediaProc->GetTicks();
	framedata.fMemoryTicks = m_pVideoProc->GetTicks();
	framedata.fFDDTicks = m_pPeriodicProc->GetTicks();
	m_pCPU->GetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
	CVideoProc::ThrOSCVars_t *pOSCV = m_pVideoProc->GetOSCVars();
	framedata.nVideoAddress = pOSCV->nVideoAddress; // видео адрес, младшие 6 бит - счётчик строк внутри строки
	framedata.bHgate = pOSCV->bHgate ? 1 : 0; // флаг отсчёта служебных видеоциклов в строке
	framedata.bVgate = pOSCV->bVgate ? 1 : 0; // флаг отсчёта служебных строк
	framedata.nVGateCounter = pOSCV->nVGateCounter; // дополнительный счётчик служебных строк
	framedata.nLineCounter = pOSCV->nLineCounter; // счётчик видео строк
	return bRet && pMsf->SetBlockFrameData(&framedata);
}

bool CPlatform::RestoreCPURegisters(CMSFManager *pMsf)
{
	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_EMT36BP = 0320;
constexpr auto BK_EMT36BP_ERRADDR = 0301;
constexpr auto BK_EMT36BP_CRCADDR = 0312;
constexpr auto BK_EMT36BP_ADDRESS = 2;
constexpr auto BK_EMT36BP_LENGTH = 4;
constexpr auto BK_EMT36BP_NAME = 6;
constexpr auto BK_EMT36BP_FOUND_ADDRESS = BK_EMT36BP_NAME + BK_NAMELENGTH;
constexpr auto BK_EMT36BP_FOUND_LENGTH = BK_EMT36BP_FOUND_ADDRESS + 2;
constexpr auto BK_EMT36BP_FOUND_NAME = BK_EMT36BP_FOUND_LENGTH + 2;

// для БК10.
bool CPlatform::EmulateLoadTape()
{
	CString strBinExt;
	strBinExt.LoadString(IDS_FILEEXT_BINARY);
	/*
	 * Известные косяки: 1) для бейсиковских бин файлов удаляет расширение .bin, как исправить
	 * непонятно, потому что никак не узнать, что мы загружаем именно бейсиковский бин файл,
	 * а не какой-либо другой. То же самое и с любыми другими файлами, по задумке авторов,
	 * имеющими расширение .bin, оно как правило удаляется.
	 * 2) при загрузке файла с автозапуском, автозапуск делается из ячейки на слово выше, чем в реальности.
	 * Это может быть критично только для очень хитрых программ.
	 **/
	bool bFileSelect = false; // что делать после диалога выбора
	bool bCancelSelectFlag = false; // флаг для усложнения алгоритма
	bool bError = false;
	bool bIsDrop = false;

	// если включена эмуляция и по этому адресу действительно ПЗУ монитора БК10
	if (g_Config.m_bEmulateLoadTape && ((GetWordIndirect(0116206) == 04767) && (GetWordIndirect(0116210) == 0426)))
	{
		uint16_t fileAddr = 0;
		uint16_t fileSize = 0;
		uint16_t abp = BK_EMT36BP;
		CString strFileName;
		// Внутренняя загрузка на БК
		uint8_t bkName[BK_NAMELENGTH] = { 0 };  // Максимальная длина имени файла на БК - BK_NAMELENGTH байтов
		uint8_t bkFoundName[BK_NAMELENGTH] = { 0 };

		if (m_strBinFileName.IsEmpty())
		{
			// если загружаем не через драг-н-дроп, то действуем как обычно
			abp = m_pCPU->GetRON(CCPU::REGISTER::R1);       // получим адрес блока параметров из R1 (BK emt 36)
			fileAddr = GetWord(abp + BK_EMT36BP_ADDRESS);   // второе слово - адрес загрузки/сохранения
			fileSize = GetWord(abp + BK_EMT36BP_LENGTH);    // третье слово - длина файла (для загрузки может быть 0)

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

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

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

		if (strFileName.SpanExcluding(_T(" ")) == _T(""))
		{
			// Если имя пустое - то покажем диалог выбора файла.
			bFileSelect = false;
l_SelectFile:
			// Запомним текущую директорию
			CString strCurDir;
			::GetCurrentDirectory(1024, strCurDir.GetBufferSetLength(1024));
			strCurDir.ReleaseBuffer();
			::SetCurrentDirectory(g_Config.m_strBinPath);
			CString strFilterBin(MAKEINTRESOURCE(IDS_FILEFILTER_BIN));
			CFileDialog dlg(TRUE, nullptr, nullptr,
			                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
			                strFilterBin, m_pVideoProc->GetScreenPtr()->GetBackgroundWindow());
			// Зададим начальной директорией директорию с Bin файлами
			dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath;

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

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

					if (!bIsDrop) // только если не дроп. там не с чем сравнивать
					{
						if (strFind.CompareNoCase(strFound) != 0 && strFindEx.CompareNoCase(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_EMT36BP_ERRADDR, 4);
								bError = true;
								bCancelSelectFlag = true; // не будем ничего делать, сразу выйдем.
							}
						}
					}
				}
			}

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

			// сейчас узнаем, нужно ли нам добавлять расширение .bin, или наоборот, удалять
			if (::GetFileExt(strFileName).CompareNoCase(strBinExt) == 0)
			{
				// у файла уже есть расширение бин
				if (!CFile::GetStatus(strCurrentPath + strFileName, fs)) // если нету файла с расширением.
				{
					CString str = ::GetFileTitle(strFileName); // удаляем расширение

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

					if (CFile::GetStatus(strCurrentPath + str, fs))  // если есть файл с расширением
					{
						strFileName = str;
					}
					else
					{
						// нет файла ни с расширением, ни без расширения
						SetByte(BK_EMT36BP_ERRADDR, 1);
						bError = 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, 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::begin);
					readAddr = 0;
					readSize = filesz;
				}

				if (!bError)
				{
					SetWord(abp + BK_EMT36BP_FOUND_ADDRESS, readAddr);
					SetWord(abp + BK_EMT36BP_FOUND_LENGTH, readSize);

					if (bkFoundName[0])
					{
						// копируем прочитанное имя файла
						for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
						{
							SetByte(abp + BK_EMT36BP_FOUND_NAME + i, bkFoundName[i]);
						}
					}
					else
					{
						// копируем прочитанное имя файла
						for (uint16_t i = 0; i < BK_NAMELENGTH; ++i)
						{
							SetByte(abp + BK_EMT36BP_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 = 0; i < readSize; ++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_EMT36BP_ERRADDR, 2);
							cs = crc;
						}
					}

					// а иначе, мы не знаем какая должна быть КС. поэтому считаем, что файл априори верный
					file.Close();
					// Заполняем системные ячейки, как это делает emt 36
					loadcrc = LOWORD(cs);
					SetWord(BK_EMT36BP_CRCADDR, loadcrc); // сохраним контрольную сумму
				}
			}
			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(BK_EMT36BP_ERRADDR, 0);
						bFileSelect = true; // включим проверку на неподходящее имя.
						goto l_SelectFile;

					// если отмена - просто выходим с заданным кодом ошибки
					case IDYES:
						// если хотим остановиться - зададим останов.
						BreakCPU();
						SetByte(BK_EMT36BP_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);  // и почистим индикацию управляющих клавиш в статусбаре
		return true; // сэмулировали
	}

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


bool CPlatform::EmulateSaveTape()
{
	CString strBinExt;
	strBinExt.LoadString(IDS_FILEEXT_BINARY);
	bool bError = false;

	// если включена эмуляция и по этому адресу действительно ПЗУ монитора БК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_EMT36BP_ADDRESS);  // второе слово - адрес загрузки/сохранения
		uint16_t fileSize = GetWord(abp + BK_EMT36BP_LENGTH);  // третье слово - длина файла (для загрузки может быть 0)

		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_EMT36BP_NAME + c);
			}

			CString strFileName = BKToUNICODE(bkName, BK_NAMELENGTH); // тут надо перекодировать  имя файла из кои8 в unicode
			strFileName.Trim(); // удаляем пробелы в конце файла, а в середине - оставляем

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

				if (dlg.DoModal() == IDOK)
				{
					g_Config.m_strBinPath = ::GetFilePath(dlg.GetPathName());
					// Получим имя
					strFileName = dlg.GetPathName();
					UNICODEtoBK(strFileName, bkName, BK_NAMELENGTH, true);
				}
				else
				{
					// Если отмена - установим флаг ошибки
					SetByte(BK_EMT36BP_ERRADDR, 4);
					bError = true;
				}
			}
			else
			{
				// Если имя не пустое
				SetSafeName(strFileName);
				strFileName = (g_Config.m_bSavesDefault ? g_Config.m_strSavesPath : g_Config.m_strBinPath) // подставляем соответствующий путь
				              + strFileName + strBinExt; // добавляем стандартное расширение для бин файлов.
			}

			CFile file;

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

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

				DWORD cs = 0; // подсчитаем контрольную сумму

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

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

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

				file.Close();
				SetWord(BK_EMT36BP_CRCADDR, LOWORD(cs)); // сохраним контрольную сумму на своё место
			}
		}

		m_pCPU->SetRON(CCPU::REGISTER::PC, 0116402); // выходим туда.
		// Refresh keyboard
		m_pMainFrame->SendMessage(WM_RESET_KBD_MANAGER);  // и почистим индикацию управляющих клавиш в статусбаре
		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::EmulateFDD()
{
	// проверим, попали ли на нужный адрес?
	if ((m_pCPU->GetRON(CCPU::REGISTER::PC) & 0177776) != m_nFDDCatchAddr)
	{
		// нет - то и не надо ничего.
		return;
	}

	CFDDStd326 *pFDD = GetFDDPtr();
	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 (int 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
	{
		auto eDrive = static_cast<FDD_DRIVE>(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
		{
			// Проверим, примонтирован ли образ в заданный привод
			bool bRes = pFDD->IsAttached(eDrive);

			// Если файл не примонтирован, выходим с ошибкой "Нет Диска"
			if (!bRes)
			{
				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));
					// высчитаем позицию.
					int pos = (((dt.TRK * nSides) + dt.SIDE) * dt.MAXSEC + (dt.SECTOR - 1)) * 512;

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

						if (length > 0)
						{
							// чтение
							for (int i = 0; i < length; ++i)
							{
								count = pFDD->GetAttachedDriveFile(eDrive)->Read(&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);
							}
						}
						else
						{
							// Если образ примонтирован, и операция записи, а образ защищён от записи
							if (pFDD->IsReadOnly(eDrive))
							{
								// то сообщим об этом ошибкой
								m_pCPU->SetPSWBit(PSW_BIT::C, true);
								SetWordIndirect(052, CFDDStd326::FDD_ERRORS::FDD_DISK_PROTECTED);
							}
							else
							{
								// запись
								length = -length;

								for (int i = 0; i < length; ++i)
								{
									word = GetWord(addr);

									try
									{
										pFDD->GetAttachedDriveFile(eDrive)->Write(&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);
								}
							}
						}
					}
					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);
}

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

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

			Sleep(10);
		}

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

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

	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;
		register auto fCPUfreq = double(m_nCPUFreq);
		m_pMediaProc->SetCPUTickTime(1.0 / fCPUfreq); // длительность одного такта в секундах
		m_pVideoProc->SetMod(fCPUfreq / 750000.0); // коэффициент пересчёта ТЧ проца в количество обращений к видеопамяти за 1c
		m_pPeriodicProc->SetMod(fCPUfreq / 15625.0);  // 15625 = 5 оборотов в сек, в одном обороте 3125 слов,
		// норма 15625.0, больше - быстрее вращение, меньше - медленнее
		m_pMediaProc->SetMod(fCPUfreq / double(g_Config.m_nSoundSampleRate)); // коэффициент пересчёта тактов процессора в медиа такты
		// означает - через сколько тактов процессора надо вставлять 1 медиа такт, т.к. число не целое, то
		// получается приблизительно.
	}
}


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

	do
	{
		// тут для отладочных целей приостанавливаем цикл.
		while (IsCPUBreaked()) // пока процессор приостановлен - ждём
		{
			if (!IsCPURun())    // если решили всё остановить,
			{
				break;          // прекращаем ждать
			}

			DrawDebugScreen();  // но продолжаем обновлять экран
			Sleep(20);          // со стандартной частотой примерно 50Гц
		}

		if (IsCPURun()) // если процессор работает, выполняем эмуляцию
		{
			// Выполняем набор инструкций
			// Если время пришло, выполняем текущую инструкцию
			if (--m_nCPUTicks <= 0)
			{
				try
				{
					// Выполняем одну инструкцию, возвращаем время выполнения одной инструкции в тактах.
					m_nCPUTicks = m_pCPU->TranslateInstruction();
					// Сохраняем текущее значение PC для отладки
					nCurrentPC = m_pCPU->GetRON(CCPU::REGISTER::PC);

					if (pBoard)
					{
						pBoard->Interception(); // перехват разных подпрограмм монитора, для их эмуляции
					}

					if (pFDD)
					{
						pFDD->Interception();
					}
				}
				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);
						int answer = g_BKMsgBox.Show(strMessage, MB_ABORTRETRYIGNORE | MB_DEFBUTTON2 | MB_ICONSTOP);

						switch (answer)
						{
							case IDIGNORE:  // если выбрали "игнорировать"
								g_Config.m_bAskForBreak = false; // то снимаем галку, и больше не вызываем диалог

							// и выполним всё, что должно выполняться для "повтор"
							case IDRETRY: // если выбрали "повтор" - то просто продолжить выполнение и посмотреть, что будет дальше
								UnbreakCPU(ADDRESS_NONE); // обратно запускаем CPU
								m_pCPU->ReplyError(); // Делаем прер. по вектору 4(halt) в следующем цикле
								break;

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

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

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

			if (pFDD)
			{
				m_pPeriodicProc->clock();
			}

			m_pVideoProc->clock();
			m_pMediaProc->clock();
		}
		else // если процессор не работает, небольшая пауза
		{
			Sleep(10);
		}
	}
	while (!m_bKillTimerEvent);     // пока не придёт событие остановки

	m_bKillTimerEvent = false;
}


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


