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

#include "stdafx.h"
#include "BK.h"
#include "Platform.h"

#include "BKErrMsgSys.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_pScreen(nullptr)
	, m_pMainFrame(nullptr)
	, m_nGotoAddress(CPlatform::ADDRESS_NONE)
	, m_nPreviousPC(CPlatform::ADDRESS_NONE)
	, m_bAskForBreak(false)
	, m_nCPUTicks(0)
	, m_nLowBound(1)
	, m_nHighBound(100000000)
	, m_pSoundBuffer(nullptr)
	, m_nFDDCatchAddr(-1)
	, m_nFDDExitCatchAddr(-1)
	, m_bBreaked(false)
	, m_bRunning(false)
	, m_bKillTimerEvent(false)
	, m_nCPUFreq_prev(-1)
{
	ClearDevicesVector();
}

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

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 (CBKMPIDevice *p : m_pMPIDevices)
	{
		SAFE_DELETE(p);
	}

	m_pMPIDevices.clear();
}

CBK001001 *CPlatform::GetBoardPtr()
{
	if (!m_bBoardFound) //искали уже?
	{
		//нет, поищем
		for (CBKMPIDevice *p : m_pMPIDevices)
		{
			//если нашли
			if (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) //искали уже?
	{
		//нет, поищем
		for (CBKMPIDevice *p : m_pMPIDevices)
		{
			//если нашли
			if (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) //искали уже?
	{
		//нет, поищем
		for (CBKMPIDevice *p : m_pMPIDevices)
		{
			//если нашли
			if (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) //искали уже?
	{
		//нет, поищем
		for (CBKMPIDevice *p : m_pMPIDevices)
		{
			//если нашли
			if (p->GetBoardModel() == DEV_MPI_IRPS)
			{
				m_pIRPS = reinterpret_cast<CIRPS *>(p); //сохраним
				break;
			}
		}

		m_bIRPSFound = true;
	}

	return m_pIRPS;
}

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

void CPlatform::InitEmulator()
{
	// инициализация текущих настроек
	m_Speaker.EnableSound(g_Config.m_bSpeaker);
	m_Speaker.SetFilter(g_Config.m_bSpeakerFilter);
	m_Covox.EnableSound(g_Config.m_bCovox);
	m_Covox.SetFilter(g_Config.m_bCovoxFilter);
	m_Covox.SetStereo(g_Config.m_bStereoCovox);
	m_AY8910.EnableSound(g_Config.m_bAY8910);
	m_AY8910.SetFilter(g_Config.m_bAY8910Filter);
	m_AY8910.SetStereo(true); //всегда стерео
	//параметры экрана
	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_Tape.SetWaveParam(g_Config.m_nSoundSampleRate, BUFFER_CHANNELS);
	m_Tape.SetPlayWavePos(0);
	m_nMediaTickCount = 0;
	m_nBufPos = 0;
}

//выносим это в отдельную процедуру, т.к. есть вероятность неудачного прочтения MSF,
//при этом надо восстановить всё как было раньше
void CPlatform::InitObjects()
{
	int nMtc = m_Sound.ReInit();   //пересоздаём звук с новыми параметрами, на выходе - длина медиафрейма в сэмплах
	m_Speaker.ReInit(); //ещё надо переинициализировать устройства, там
	m_Speaker.ConfigureTapeBuffer(nMtc);	//переопределяем буферы в зависимости от текущей частоты дискретизации
	m_Covox.ReInit();   //есть вещи, зависящие от частоты дискретизации,
	m_AY8910.ReInit();  //которая теперь величина переменная. Но требует перезапуска конфигурации.
	//переменные для медиа системы
	m_nMediaTicksPerFrame = nMtc;
	m_nSoundBufferLen = nMtc * SAMPLE_LENGTH;
	SAFE_DELETE_ARRAY(m_pSoundBuffer);
	m_pSoundBuffer = new uint8_t[m_nSoundBufferLen];

	if (!m_pSoundBuffer)
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}

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

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

	EnableAskForBreak(g_Config.m_bAskForBreak);
}


void CPlatform::StartPlayTape()
{
	StartPlayTape(g_Config.m_strTapePath + m_strTapeFileName);
}

void CPlatform::StartPlayTape(const CString &strPath)
{
	CString strExt = g_Config.GetFileExt(strPath);
	TAPE_FILE_INFO tfi;
	memset(&tfi, 255, sizeof(TAPE_FILE_INFO));

	if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE))))
	{
		m_Tape.LoadWaveFile(strPath);
	}
	else if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE))))
	{
		m_Tape.LoadMSFFile(strPath);
	}
	else if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY))))
	{
		m_Tape.LoadBinFile(strPath, &tfi);
	}
	else
	{
		return;
	}

	m_Tape.GetWaveFile(&tfi); //вычисляем и заполняем внутренние переменные
	m_Tape.SetPlayWavePos(0);
	m_Tape.StartPlay();
}

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

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

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

	if (!pBoard)
	{
		return;
	}

	if (IsCPURun()) //защита от множественного вызова функции.
	{
		StopCPU();
		BK_BOARD_MODEL fdd_model = (GetFDDPtr()) ? GetFDDPtr()->GetBoardModel() : DEV_MPI_NONE;

		//если контроллер А16М и сделали длинный ресет или контроллер СМК512 - делаем перезапуск
		//с адреса, который задаётся контроллером.
		if (
		    ((fdd_model == DEV_MPI_A16M) && bLongreset) ||
		    (fdd_model == DEV_MPI_SMK512)
		)
		{
			auto a16m = reinterpret_cast<CFDD_A16M *>(GetFDDPtr());
			a16m->SetAltProMode(ALTPRO_A16M_START_MODE);
			a16m->SetAltProCode(0);
			pBoard->ResetCold(0);
		}
		else
		{
			//если у нас БК11, то можно реализовать СУ/ресет - перезапуск по 100000
			if ((pBoard->GetBoardModel() != 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 (CBKMPIDevice *p : m_pMPIDevices)  //пройдёмся по вектору
	{
		if (p) //если есть устройство
		{
			if (!p->InitDevice(g_Config.m_nCPURunAddr))
			{
				return false;
			}
		}
	}

	FrameParam();
	// если ресет не удался - значит не удалось проинициализировать
	// память - значит не удалось загрузить какие-то дампы ПЗУ -
	// значит дальше работать невозможно.
	m_CPU.InitCPU();
	//ещё косяк - в функции помимо инициализации делается ресет устройства.
	//а в функции InitCPU вызывается ResetPlatform, который делает только ресет устройства
	// получается дублирование ресетов, лишнее действие.
	// хоть это не критично, и эти функции делались немного для разных целей, ресет нужен
	// и там, и там, но вот вместе они дают такой эффект.
	m_Debugger.InitParameters();
	return true;
}

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


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

bool CPlatform::DropFiles(CString *strPath)
{
	ClearProcessingFiles();
	CString strFilePath = g_Config.GetFilePath(*strPath);
	CString strFileExt = g_Config.GetFileExt(*strPath);

	if (!strFileExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY))))  // Binary file
	{
		m_strBinFileName = g_Config.GetFileName(*strPath);

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

		m_strScriptFileName = _T("autorun\\monitor_load.bkscript");
		g_Config.m_bEmulateLoadTape = true;
	}
	else if (!strFileExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_MEMORYSTATE))))  // Memory state file
	{
		m_strMemFileName = g_Config.GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strMemPath = strFilePath;
		}
	}
	else if (strFileExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE))) == 0
	         || strFileExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE))) == 0)  // BK tape file
	{
		m_strTapeFileName = g_Config.GetFileName(*strPath);

		if (!strFilePath.IsEmpty())
		{
			g_Config.m_strTapePath = strFilePath;
		}
	}
	else if (!strFileExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_SCRIPT))))  // Initial Script file
	{
		m_strScriptFileName = g_Config.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('\"')); //а ещё кавычки

		switch (command)
		{
			case _T('B'): // Binary file
			{
				m_strBinFileName = g_Config.GetFileName(strParam);
				CString strFilePath = g_Config.GetFilePath(strParam);

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

				m_strScriptFileName = _T("autorun\\monitor_load.bkscript");
				g_Config.m_bEmulateLoadTape = true;
			}
			break;

			case _T('M'): // Memory state file
			{
				m_strMemFileName = g_Config.GetFileName(strParam);
				CString strFilePath = g_Config.GetFilePath(strParam);

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

			case _T('T'): // BK tape file
			{
				m_strTapeFileName = g_Config.GetFileName(strParam);
				CString strFilePath = g_Config.GetFilePath(strParam);

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

			case _T('S'): // Initial Script file
			{
				m_strScriptFileName = g_Config.GetFileName(strParam);
				CString strFilePath = g_Config.GetFilePath(strParam);

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

			case _T('?'):
			case _T('H'):
			{
				CString strTitle;
				strTitle.LoadString(IDS_EMUL_TITLE);
				CString strPrompt;
				strPrompt.LoadString(IDS_COMMAND_PROMPT_INFO);
				CBKMessageBox(strTitle + strPrompt, MB_OK);
				return true;
			}

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

	return true;
}

void CPlatform::SetScriptPath(bool bXlat)
{
	m_Script.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_BOARD_MODEL device)
{
	CBKMPIDevice *pDev = nullptr;
	bool bFail = true;

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

		case DEV_MPI_BK0011:
			pDev = new CBK0011();
			break;

		case DEV_MPI_BK0011M:
			pDev = new CBK0011M();
			break;

		case DEV_MPI_NONE:
			bFail = false;
			pDev = nullptr;
			break;

		case DEV_MPI_MSTD10:
			pDev = new CBKMSTD10();
			break;

		case DEV_MPI_MSTD10_EXT32:
			pDev = new CBKMSTD10_EXT32();
			break;

		case DEV_MPI_MSTD11:
			pDev = new CBKMSTD11();
			break;

		case DEV_MPI_MSTD11M:
			pDev = new CBKMSTD11M();
			break;

		case DEV_MPI_STD_326:
			pDev = new CFDDStd326();
			break;

		case DEV_MPI_STD_253:
			pDev = new CFDDStd253();
			break;

		case DEV_MPI_STD_MEM:
			pDev = new CFDDStd326Mem();
			break;

		case DEV_MPI_A16M:
			pDev = new CFDD_A16M();
			break;

		case DEV_MPI_SMK512:
			pDev = new CFDD_SMK512();
			break;

		case DEV_MPI_SAMARA:
			pDev = new CFDD_Samara();
			break;

		default:
			ASSERT(FALSE);
	}

	if (bFail && !pDev)
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}

	return pDev;
}

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

	if (pDev)
	{
		m_pMPIDevices.push_back(pDev);
	}
	else
	{
		//если не создалась мат. плата - то лучше ничего дальше не делать
		return false;
	}

	pDev = CreateDevice(g_Config.GetMPIDeviceModel());

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

	//ИРПС
	pDev = new CIRPS();

	if (pDev)
	{
		m_pMPIDevices.push_back(pDev);
	}
	else
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}

	return true;
}

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_CPU.GetRON(CCPU::R_PC);
	register uint16_t instr = GetWordIndirect(pc);
	register uint16_t NextAddr = 0;

	if (m_Debugger.IsInstructionOver(instr))
	{
		RunToAddr(pc + m_Debugger.CalcInstructionLength(instr));
	}
	else if ((NextAddr = m_Debugger.CalcNextAddr(pc)) != ADDRESS_NONE)
	{
		RunToAddr(NextAddr);
	}
	else
	{
		RunInto();
	}
}

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


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


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



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); //отменяем отладочную приостановку, для случая, когда мы останавливаем
		//проц чтобы изменить конфигурацию, загрузить состояние или выйти из программы
	}
}

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



uint8_t CPlatform::GetByte(uint16_t addr)
{
	register uint8_t b2 = 0;
	register bool res = false;

	for (CBKMPIDevice *p : m_pMPIDevices)
	{
		if (p)
		{
			register uint8_t b1 = 0;
			res |= p->GetByte(addr, &b1);
			b2 |= b1;
		}
	}

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

	return 0;
}

uint16_t CPlatform::GetWord(uint16_t addr)
{
	register uint16_t w2 = 0;
	register bool res = false;

	for (CBKMPIDevice *p : m_pMPIDevices)
	{
		if (p)
		{
			register uint16_t w1 = 0;
			res |= p->GetWord(addr, &w1);
			w2 |= w1;
		}
	}

	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)
{
	register bool res = false;

	for (CBKMPIDevice *p : m_pMPIDevices)
	{
		if (p)
		{
			res |= p->SetByte(addr, b);
		}
	}

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

void CPlatform::SetWord(uint16_t addr, uint16_t w)
{
	register bool res = false;

	for (CBKMPIDevice *p : m_pMPIDevices)
	{
		if (p)
		{
			res |= p->SetWord(addr, w);
		}
	}

	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_CPU.GetSysRegs(addr);
		return (addr & 1) ? HIBYTE(w) : LOBYTE(w);
	}

	register uint8_t b2 = 0;

	for (CBKMPIDevice *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_CPU.GetSysRegs(addr);
		return w;
	}

	register uint16_t w2 = 0;

	for (CBKMPIDevice *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_CPU.SetSysRegsInternal(addr, b);
	}
	else
	{
		for (CBKMPIDevice *p : m_pMPIDevices)
		{
			if (p)
			{
				p->SetByteIndirect(addr, b);
			}
		}
	}
}

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


void CPlatform::KeyboardInterrupt(uint16_t interrupt)
{
	if ((GetBoardPtr()->m_reg177660 & 0100) == 0)
	{
		//если прерывания разрешены, делаем прерывание
		m_CPU.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; //если мегакомбо, для увеличения - увеличиваем в 2 раза
		m_nCPUFreq += (n = CalcStep()) ? n : m_nCPUFreq;

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

void CPlatform::SlowdownCPU()
{
	if (m_nCPUFreq > m_nLowBound)
	{
		register int n; //если мегакомбо, для уменьшения - уменьшаем в 2 раза
		m_nCPUFreq -= (n = CalcStep()) ? n : 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;
	//временно выгрузим все образы дискет.
	CFDDStd326 *pFDD = GetFDDPtr();

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

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

	//!!! Здесь сделано допущение, могущее привести к серьёзной ошибке!
	// в MSF_PLATFORM_DATA порядок списка моделей устройств может не совпадать с порядком,
	// который создаётся в CreateBoard, и хоть пока это так и есть. Но так может быть не всегда.
	//Иначе, нужно будет переписать эту функцию вообще по-другому.

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

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

				pOldMPIDevices.clear();
				bRet = true;
			}
		}
	}
	else
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}

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

	SAFE_DELETE(pPD);

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

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

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

	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 (CBKMPIDevice *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 = g_Config.GetBKModelNumber();
			auto t = 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 (CBKMPIDevice *p : m_pMPIDevices)  //пройдёмся по вектору
					{
						if (p) //если есть устройство
						{
							(*pPD)->pDevices[n].nBKBoardModel = p->GetBoardModel(); //получим его тип
							(*pPD)->pDevices[n].nUniqueID = nUniqueID++; //и на всякий случай идентификатор
							n++; //счётчик сосчитает  устройство
						}

						// если вектор будет содержать null вместо указателя на устройство,
						// то надо будет скорректировать кол-во устройств
					}
				}
				else
				{
					CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
				}
			}

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

	return false;
}

bool CPlatform::RestorePreview(CMSFManager *pMsf)
{
	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->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_fMediaTicks = framedata.fMediaTicks;
			m_fMemoryTicks = framedata.fMemoryTicks;
			m_fFDDTicks = framedata.fFDDTicks;
			m_CPU.SetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
			m_nVideoAddress = framedata.nVideoAddress & 0xffff; //видео адрес, младшие 6 бит - счётчик строк внутри строки
			m_bHgate = (framedata.bHgate != 0); //флаг отсчёта служебных видеоциклов в строке
			m_bVgate = (framedata.bVgate != 0); //флаг отсчёта служебных строк
			m_nVGateCounter = framedata.nVGateCounter; //дополнительный счётчик служебных строк
			m_nLineCounter = framedata.nLineCounter & 0377; //счётчик видео строк
		}
		else
		{
			m_nCPUTicks = 0;
			m_fMediaTicks = 0.0;
			m_fMemoryTicks = 0.0;
			m_fFDDTicks = 0.0;
			m_nVideoAddress = 0; //видео адрес, младшие 6 бит - счётчик строк внутри строки
			m_bHgate = false;
			m_bVgate = true;
			m_nVGateCounter = 64;
			m_nLineCounter = 0; //счётчик видео строк
		}

		return true; //если конфиг не прочёлся, значит этого блока просто нет. ничего страшного
	}
	else
	{
		bool bRet = pMsf->SetBlockConfig();
		MSF_FRAMEDATA framedata;
		framedata.nCPUTicks = m_nCPUTicks;
		framedata.fMediaTicks = m_fMediaTicks;
		framedata.fMemoryTicks = m_fMemoryTicks;
		framedata.fFDDTicks = m_fFDDTicks;
		m_CPU.GetTimerSpeedInternal(framedata.nTimerSpeed, framedata.nTimerDiv);
		framedata.nVideoAddress = m_nVideoAddress; //видео адрес, младшие 6 бит - счётчик строк внутри строки
		framedata.bHgate = m_bHgate ? 1 : 0; //флаг отсчёта служебных видеоциклов в строке
		framedata.bVgate = m_bVgate ? 1 : 0; //флаг отсчёта служебных строк
		framedata.nVGateCounter = m_nVGateCounter; //дополнительный счётчик служебных строк
		framedata.nLineCounter = m_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::R_R0, cpu_reg.r0);
		GetCPUPtr()->SetRON(CCPU::R_R1, cpu_reg.r1);
		GetCPUPtr()->SetRON(CCPU::R_R2, cpu_reg.r2);
		GetCPUPtr()->SetRON(CCPU::R_R3, cpu_reg.r3);
		GetCPUPtr()->SetRON(CCPU::R_R4, cpu_reg.r4);
		GetCPUPtr()->SetRON(CCPU::R_R5, cpu_reg.r5);
		GetCPUPtr()->SetRON(CCPU::R_SP, cpu_reg.sp);
		GetCPUPtr()->SetRON(CCPU::R_PC, cpu_reg.pc);
		GetCPUPtr()->SetPSW(cpu_reg.psw);
	}
	else
	{
		cpu_reg.r0 = GetCPUPtr()->GetRON(CCPU::R_R0);
		cpu_reg.r1 = GetCPUPtr()->GetRON(CCPU::R_R1);
		cpu_reg.r2 = GetCPUPtr()->GetRON(CCPU::R_R2);
		cpu_reg.r3 = GetCPUPtr()->GetRON(CCPU::R_R3);
		cpu_reg.r4 = GetCPUPtr()->GetRON(CCPU::R_R4);
		cpu_reg.r5 = GetCPUPtr()->GetRON(CCPU::R_R5);
		cpu_reg.sp = GetCPUPtr()->GetRON(CCPU::R_SP);
		cpu_reg.pc = GetCPUPtr()->GetRON(CCPU::R_PC);
		cpu_reg.psw = GetCPUPtr()->GetPSW();

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

	return true;
}


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

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

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

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

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

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

		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_pScreen->GetBackgroundWindow());
			// Зададим начальной директорией директорию с Bin файлами
			dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath;

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

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

					if (strFind.CompareNoCase(strFound) != 0)
					{
						CBKMessageBox message;
						int result = message.Show(_T("Не тот файл подсовываете. Повторим?"), MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);

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

			// восстановим текущую директорию
			::SetCurrentDirectory(strCurDir);
			// Refresh keyboard
			m_kprs.clear(); //очистим всё, что было нажато - отменяем.
			GetBoardPtr()->m_reg177660 &= ~0200;  // ещё удалим флаг готовности нового кода
			m_pMainFrame->SendMessage(WM_TAPE_FILE_LOAD);  //и почистим индикацию управляющих клавиш в статусбаре
		}
		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; //получим информацию о файле - таким образом проверяется существует он или нет

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

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

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

			strFileName = strCurrentPath + strFileName;
		}

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

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

				if (readSize != filesz - 4)
				{
					if (readSize == filesz - 22)
					{
						file.Read(bkFoundName, 16); //надо пропустить ненужные данные.
					}
					else
					{
						//то ошибка, непонятный формат бин, или нет конца файла
						SetByte(0301, 2);
						bError = true;
					}
				}

				if (!bError)
				{
					SetWord(abp + 026, readAddr);
					SetWord(abp + 030, readSize);

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

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

					SetWord(0264, fileAddr);
					SetWord(0266, readSize);
					DWORD cs = 0; //подсчитаем контрольную сумму

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

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

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

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

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

		// Помещаем в R5 последний адрес, куда производилось чтение, как в emt 36
		m_CPU.SetRON(CCPU::R_R5, fileAddr);
		m_CPU.SetRON(CCPU::R_PC, 0116710); //выходим туда.
		return true; //сэмулировали
	}

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

bool CPlatform::EmulateSaveTape()
{
	bool bError = false;

	//если включена эмуляция и по этому адресу действительно ПЗУ монитора БК10
	if (g_Config.m_bEmulateSaveTape && ((GetWordIndirect(0116170) == 04767) && (GetWordIndirect(0116172) == 062)))
	{
		// получим адрес блока параметров из R1 (BK emt 36)
		WORD abp = m_CPU.GetRON(CCPU::R_R1);
		WORD fileAddr = GetWord(abp + 2);  // второе слово - адрес загрузки/сохранения
		WORD fileSize = GetWord(abp + 4);  // третье слово - длина файла (для загрузки может быть 0)

		if (fileSize)
		{
			BYTE bkName[16];   // Максимальная длина имени файла на БК - 16 байтов

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

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

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

				if (dlg.DoModal() == IDOK)
				{
					g_Config.m_strBinPath = g_Config.GetFilePath(dlg.GetPathName());
					// Получим имя
					strFileName = dlg.GetPathName();
				}
				else
				{
					// Если отмена - установим флаг ошибки
					SetByte(0301, 4);
					bError = true;
				}

				// Refresh keyboard
				m_kprs.clear(); //очистим всё, что было нажато - отменяем.
				GetBoardPtr()->m_reg177660 &= ~0200;  // ещё удалим флаг готовности нового кода
				m_pMainFrame->SendMessage(WM_TAPE_FILE_LOAD);  //и почистим индикацию управляющих клавиш в статусбаре
			}
			else
			{
				// Если имя не пустое
				strFileName = (g_Config.m_bSavesDefault ? g_Config.m_strSavesPath : g_Config.m_strBinPath) //подставляем соответствующий путь
				              + strFileName + _T(".bin"); //добавляем стандартное расширение для бин файлов.
			}

			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)); //слово длины
				DWORD cs = 0; //подсчитаем контрольную сумму

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

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

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

		m_CPU.SetRON(CCPU::R_PC, 0116402); //выходим туда.
		return true; //сэмулировали
	}

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




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

	//дополнительная проверка на правильную прошивку.
	if (GetWordIndirect(m_nFDDCatchAddr) == 0010663
	        && GetWordIndirect(m_nFDDCatchAddr + 2) == 0000050
	        && GetWordIndirect(m_nFDDCatchAddr + 4) == 0106763
	        && GetWordIndirect(m_nFDDCatchAddr + 6) == 0000052
	        && GetWordIndirect(m_nFDDCatchAddr + 010) == 0012700
	        && GetWordIndirect(m_nFDDCatchAddr + 012) == 0000004
	        && GetWordIndirect(m_nFDDCatchAddr + 014) == 0011063
	        && GetWordIndirect(m_nFDDCatchAddr + 016) == 0000046
	        && GetWordIndirect(m_nFDDCatchAddr + 020) == 0010710
	        && GetWordIndirect(m_nFDDCatchAddr + 022) == 0062710
	   )
	{
		//всё ок
	}
	else
	{
		//нестандартная прошивка
		m_nFDDCatchAddr = 0177777;
		m_nFDDExitCatchAddr = 0177777;
	}
}


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

	CFDDStd326 *pFDD = GetFDDPtr();
	CFDDStd326::TABLE_EMFDD dt;
	auto wdt = reinterpret_cast<uint16_t *>(&dt); //структура в виде массива слов.
	uint16_t table_addr = m_CPU.GetRON(CCPU::R_R3);
	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_CPU.SetPSWBit(PSW_C_POS, true);
		SetWordIndirect(052, CFDDStd326::FDD_0_TRACK_ERROR);
	}
	else
	{
		int nSides = 2;

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

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

		// Если длина  чтения/записи равна 0 - просто выходим без ошибок
		if (!dt.WCNT)
		{
			m_CPU.SetPSWBit(PSW_C_POS, false);
			SetWordIndirect(052, CFDDStd326::FDD_NOERROR);
		}
		else
		{
			// Проверим, примонтирован ли образ в заданный привод
			bool bRes = pFDD->IsAttached(drive);

			// Если файл не примонтирован, выходим с ошибкой "Нет Диска"
			if (!bRes)
			{
				m_CPU.SetPSWBit(PSW_C_POS, true);
				SetWordIndirect(052, CFDDStd326::FDD_NO_DISK);
			}
			else
			{
				bool bErrors = false;

				if ((dt.SECTOR == 0 || dt.SECTOR > dt.MAXSEC) || (dt.SIDE >= 2) || (dt.TRK >= 82))
				{
					bErrors = true;
					m_CPU.SetPSWBit(PSW_C_POS, true);
					SetWordIndirect(052, CFDDStd326::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(drive)->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(drive)->Read(&word, sizeof(uint16_t));
								SetWordIndirect(addr, word);

								if (count != sizeof(uint16_t))
								{
									m_CPU.SetPSWBit(PSW_C_POS, true);
									SetWordIndirect(052, CFDDStd326::FDD_STOP);
									bErrors = true;
									break;
								}

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

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

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

									addr += sizeof(uint16_t);
								}
							}
						}
					}
					else
					{
						bErrors = true;
						m_CPU.SetPSWBit(PSW_C_POS, true);
						SetWordIndirect(052, CFDDStd326::FDD_BAD_FORMAT);
					}
				}

				if (!bErrors)
				{
					m_CPU.SetPSWBit(PSW_C_POS, false);
					SetWordIndirect(052, CFDDStd326::FDD_NOERROR);
				}
			}
		}
	}

	m_CPU.SetRON(CCPU::R_PC, m_nFDDExitCatchAddr);
}

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

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

	CBKMessageBox(g_pstrErrors[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_fCpuTickTime = 1.0 / fCPUfreq; //длительность одного такта в секундах
		m_fMemory_Mod = fCPUfreq / 750000.0;      //коэффициент пересчёта ТЧ проца в количество обращений к видеопамяти за фрейм
		m_fFDD_Mod = fCPUfreq / 15625.0;  //15625 = 5 оборотов в сек, в одном обороте 3125 слов,
		//норма 15625.0, больше - быстрее вращение, меньше - медленнее
		m_fMedia_Mod = fCPUfreq / double(g_Config.m_nSoundSampleRate); //коэффициент пересчёта тактов процессора в медиа такты
		//означает - через сколько тактов процессора надо вставлять 1 медиа такт, т.к. число не целое, то
		//получается приблизительно.
	}
}


void CPlatform::TimerThreadFunc()
{
	auto pBoard = GetBoardPtr();
	auto pFDD = GetFDDPtr();

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

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

		if (IsCPURun()) //если процессор работает, выполняем эмуляцию
		{
			register uint16_t &r177716in = pBoard->m_reg177716in;

			if (m_kprs.nScanCode == -1) //сканкод пуст, значит все нажатые клавиши отпущены
			{
				// Установим в регистре 177716 флаг отпускания клавиши
				r177716in |= 0100;
			}
			else //если не пуст, то что-то остаётся нажатым ещё
			{
				// Установим в регистре 177716 флаг нажатия клавиши
				r177716in &= ~0100;
			}

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

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

					if (pFDD)
					{
						pFDD->Interception();
					}
				}
				catch (CExceptionHalt &halt)
				{
					// BK Violation exception. Например запись в ПЗУ или чтение из несуществующей памяти
					if (m_bAskForBreak)
					{
						BreakCPU(); // Останавливаем CPU для отладки
						CString strMessage;
						strMessage.Format(IDS_ERRMSG_ROM, halt.m_addr, m_nPreviousPC, halt.m_info);
						CBKMessageBox message;
						int answer = message.Show(strMessage, MB_ABORTRETRYIGNORE | MB_DEFBUTTON2 | MB_ICONSTOP);

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

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

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

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

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

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

			if (--m_fMediaTicks <= 0.0)
			{
				do 
				{
					MediaTick(); //тут делается звучание всех устройств и обработка прочих устройств
					m_fMediaTicks += m_fMedia_Mod;
				} while (m_fMediaTicks < 1.0);
			}

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

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

			if (m_kprs.bProcessed) //если последнюю из нажатых клавишу отпустили
			{
				TRACE1("pop char %d\n", m_kprs.nScanCode);
				m_kprs.clear(); //то всё очищаем
			}
		}
		else
		{
			Sleep(10);
		}
	}
	while (!m_bKillTimerEvent);         //пока не придёт событие остановки

	m_bKillTimerEvent = false;
}


void CPlatform::MediaTick()
{
	//обработка медиатактов
	if (m_nMediaTickCount == 0)
	{
		// Получаем сохранённые 1/CPU_FRAMERATE секунды звуковые данные
		// Берём данные с ленты -> m_pSoundBuffer, а если нечего брать, очищаем буфер
		m_Tape.PlayWaveGetBuffer(m_pSoundBuffer, m_nSoundBufferLen);
		// Посылаем данные с ленты в 177716 <- m_pSoundBuffer
		m_Speaker.ReceiveTapeBuffer(m_pSoundBuffer, m_nSoundBufferLen);
		m_nBufPos = 0;
	}

	//осуществляем приём данных с магнитофона, так он будет работать даже если спикер выключен
	register uint16_t &r177716in = GetBoardPtr()->m_reg177716in;

	if (m_Speaker.GetTapeSample())
	{
		r177716in |= 040;
	}
	else
	{
		r177716in &= ~040;
	}

	register auto pSTBuffer = reinterpret_cast<SAMPLE_TYPE *>(m_pSoundBuffer);
	register double fL = 0.0, fR = 0.0;
	m_Sound.SoundGen_ResetSample(pSTBuffer[m_nBufPos], pSTBuffer[m_nBufPos + 1]); //задаём начальные уровни для микширования

	// Берём звуки со спикера BK -> m_pSoundBuffer
	if (m_Speaker.IsSoundEnabled())
	{
		m_Speaker.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR); //микшируем сэмплы внутри m_Sound
	}

	// Берём данные с ковокса -> m_pSoundBuffer
	if (m_Covox.IsSoundEnabled())
	{
		m_Covox.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR);
	}

	// Берём данные с ay8910 -> m_pSoundBuffer
	if (m_AY8910.IsSoundEnabled())
	{
		m_AY8910.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR);
	}

	register SAMPLE_TYPE L, R;
	m_Sound.SoundGen_FeedDAC_Mixer(L, R); //получаем сюда смикшированное, а там - осуществляем звучание
	pSTBuffer[m_nBufPos++] = L; //это нужно в основном для осциллографа
	pSTBuffer[m_nBufPos++] = R;

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


//это вариант точного алгоритма из VP1-037
void CPlatform::Make_One_Screen_Cycle()
{
	register uint16_t dww = m_nVideoAddress & 076; // счётчик слов внутри строки
	register uint16_t dwa = m_nVideoAddress & 037700; //адрес строки
	register auto pBoard = GetBoardPtr();

	if (!(m_bVgate || m_bHgate))
	{
		m_pScreen->SetExtendedMode(!(pBoard->m_reg177664 & 01000));
		register DWORD_PTR nScrAddr = ((DWORD_PTR)pBoard->GetScreenPage()) << 14;
		register uint8_t *nScr = pBoard->GetMainMemory() + nScrAddr + m_nVideoAddress;
		m_pScreen->PrepareScreenLineWordRGB32(m_nLineCounter, dww, *(uint16_t *)nScr);
	}

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

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

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

			//когда считаем служебные строки, выбираем из них 353..350
			if ((m_nVGateCounter & 0374) == 050)    //if (050 <= m_nVGateCounter && m_nVGateCounter <= 053)
			{
				//если строки 351, 350
				if (!(m_nVGateCounter & 2))         //if (050 == m_nVGateCounter || m_nVGateCounter == 051)
				{
					//применяем новое смещение
					dwa = (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_bVgate = true;
				m_nVGateCounter = 64;

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

				m_pMainFrame->PostMessage(WM_SCR_DRAW);
			}

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

		m_nLineCounter = dwl;
	}

	m_nVideoAddress = (dwa | dww) & 037776;
}


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


