/*************************************************
*           BK Emulator v3.8 for Windows         *
*************************************************/

/*
File: Board.cpp
Version: 1.0.0
Written by: Yuriy Kalmykov <kalmykov@stoik.com>
    Copyright (c) 2002-2004 Yuriy Kalmykov

    BK Emulator is a program emulated hardware environment for running
code for BK 0010(01) in different configurations.

    This code may be used in compiled form in any way you desire.
This file or it's parts can't be redistributed without the authors
written consent, but can be modified for your private needs.
    Providing that this notice and the authors name and all copyright
notices remains intact.

    Please, an email me to know how you are using it and where. You can
ask me for any information about this below code or any attendant
knowledge.

    This file is provided "as is" with no expressed or implied warranty.
The author accepts no liability for any damage or loss of business that
this product may cause.
*/


// Chip.cpp: implementation of the CMotherBoard class.
//


#include "stdafx.h"
#include "Ini.h"
#include "Board.h"
#include "BKErrMsgSys.h"

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

//////////////////////////////////////////////////////////////////////
// Construction/Destruction

CMotherBoard::CMotherBoard(BOARD_MODEL model)
	: m_pParent(nullptr)   // Указатель на окно, в которое посылаются оповещающие сообщения
	, m_pTimerThread(nullptr)
	, m_pScreen(nullptr)
	, m_pSpeaker(nullptr)
	, m_pCovox(nullptr)
	, m_pAY8910(nullptr)
	, m_pDebugger(nullptr)
	, m_bAskForBreak(false)
	, m_bBlockStop(false)
	, m_nGotoAddress(ADDRESS_NONE)
	, m_nCPUTicks(0)
	  //переменные для эмуляции экрана
	, m_pScreenBufferAddress(nullptr)
	, m_nScreenCycleCount(0) //нужно обнулять при создании нового экземпляра
	, m_nScreenLinesCount(0)
	, m_nScreenLineBytesCount(0)
	, m_nScreenStage(0)
	, m_nScreenLinesStage(0)
	, m_nScreenLineCycleCount(0)
	  //переменные,задающие конкретную модель
	, m_BoardModel(model)
	, m_nStartAddr(0100000)
	, m_nBKPortsIOArea(BK_PURE_PORTSIO_AREA)
{
	SetCPUBaseFreq(CPU_SPEED_BK10); //частота задаётся этой константой
	ZeroMemory(m_MemoryMap, sizeof(m_MemoryMap));
	m_cpu.AttachChip(this);
	m_fdd.AttachParent(this);
	m_fdd.init_A16M_10(&m_ConfBKModel, ALTPRO_A16M_STD10_MODE);
	//переменные для медиа системы
	m_nMediaTicksPerFrame = g_Config.m_nSoundSampleRate / CPU_FRAMERATE;
	m_nLowBound = m_nMediaTicksPerFrame * CPU_FRAMERATE * 10;
	m_nHighBound = m_nLowBound * 100;
	m_nSoundBufferLen = m_nMediaTicksPerFrame * SAMPLE_LENGTH;
	m_pSoundBuffer = new BYTE[m_nSoundBufferLen];
	m_nMediaTickCount = 0;
	m_nBufPos = 0;
	// Инициализация модуля памяти
	m_pMemory = new BYTE [0200000];
	ZeroMemory(m_pMemory, 0200000);
	InitMemoryValues();
	Reset();
}

CMotherBoard::~CMotherBoard()
{
	SAFE_DELETE_ARRAY(m_pSoundBuffer);
	SAFE_DELETE_ARRAY(m_pMemory);
}

void CMotherBoard::AttachWindow(CMainFrame *pParent)
{
	m_pParent = pParent;
	//установку данных буфера достаточно сделать один раз.
	// если осциллограф рисуется через opengl, то там же создаётся контекст.
	// поэтому, если установка буфера будет куда-нибудь перенесена, то контекст для opengl
	// нужно будет создавать отсюда вручную.
	m_pParent->GetOscillatorViewPtr()->SetBuffer(m_pSoundBuffer, m_nSoundBufferLen);
}

void CMotherBoard::InitMemoryValues()
{
	WORD val = 0;
	BYTE flag = 0;

	for (int i = 0; i < 0100000; i += 2, flag--)
	{
		*(WORD *)&m_pMemory[i] = val;
		val = ~val;

		if (flag == 192)
		{
			val = ~val;
			flag = 0;
		}
	}
}


void CMotherBoard::OnReset()
{
	if (m_pScreen)
	{
		m_pScreen->SetPalette(0);
		m_pScreen->SetRegister(m_reg177664);
	}

	m_cpu.ResetCPU();
	m_nCPUTicks = 0;
	m_fTickCount = 0.0;
	m_fMediaTicks = 0.0;
	m_fMemoryTicks = 0.0;
	m_fFDDTicks = 0.0;
}


/*
В функциях GetByte, GetWord, SetByte, SetWord доступ к регистрам 0177700..0177712
не обрабатывается, т.к. эти функции должны вызываться только из CPU, и доступ к
регистрам обрабатывается там.
хоть и должны, но кое-где ещё всё равно вызываются, напр. в Reset и RefreshScreen
*/

BYTE CMotherBoard::GetByte(WORD addr)
{
	register int nBank = (addr >> 12) & 0x0f;
	register BYTE v;

	// Сперва проверим, на системные регистры
	if (nBank == 15)
	{
		if (addr >= m_nBKPortsIOArea)
		{
			switch (GetFDDType())
			{
				case DEV_FDD_SMK512:
					if (GetAltProMode() == ALTPRO_SMK_SYS_MODE || GetAltProMode() == ALTPRO_SMK_ALL_MODE)
						//в режиме SYS и ALL в этом диапазоне можно читать
					{
						goto gblb1;
					}
					else
					{
						if (OnGetSystemRegister(addr, &v, true))
						{
							return v;
						}

						//иначе, в этом диапазоне действуют обычные правила
						goto gblb2;
					}

					break;

				case DEV_FDD_A16M:
					if (GetAltProMode() == ALTPRO_A16M_BASIC_MODE)
					{
						//ещё одно исключение. в режиме бейсика m_nBKPortsIOArea == BK_PURE_PORTSIO_AREA целы 0600 байтов ещё
						if ((addr < BK_PURE_PORTSIO_AREA) && (GetAltProExtCode() & 010)) //если бейсик подключен
						{
							goto gblb1;
						}
					}
					else if (GetAltProMode() == ALTPRO_A16M_START_MODE)
					{
						//в режиме Start в этом диапазоне можно читать
gblb1:

						if (OnGetSystemRegister(addr, &v, true))
						{
							return v | m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
						}
						else
						{
							return m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
						}
					}

				//иначе, как в обычном режиме, смотрим порты
				default:

					//в обычном режиме тут только регистры могут быть
					if (OnGetSystemRegister(addr, &v, true))
					{
						return v;
					}
					else
					{
						throw CExceptionHalt(addr, _T("Can't read this address."));
					}

					break;
			}
		}
		else
		{
			//по адресам 170000-177000 для SMK если режим не SYS - всегда ОЗУ
			if (GetAltProMode() != ALTPRO_A16M_START_MODE && GetFDDType() == DEV_FDD_SMK512)
			{
				return m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
			}
		}
	}

gblb2:

	//теперь проверим на чтение из несуществующего банка
	if (m_MemoryMap[nBank].bReadable)
	{
		//если читать можно, заодно и ремап сделаем, может не понадобится переопределять везде эту функцию
		return m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't read this address."));
	}
}


WORD CMotherBoard::GetWord(WORD addr)
{
	register int nBank = (addr >> 12) & 0x0f;
	register WORD num = addr; //специально для регистров hdd smk512
	register WORD v;
	addr &= 0177776;

	// Сперва проверим, на системные регистры
	if (nBank == 15)
	{
		if (addr >= m_nBKPortsIOArea)
		{
			switch (GetFDDType())
			{
				case DEV_FDD_SMK512:
					if (GetAltProMode() == ALTPRO_SMK_SYS_MODE || GetAltProMode() == ALTPRO_SMK_ALL_MODE)
						//в режиме SYS и ALL в этом диапазоне можно читать
					{
						goto gwlb1;
					}
					else
					{
						if (OnGetSystemRegister(num, &v, false))
						{
							return v;
						}

						//иначе, в этом диапазоне действуют обычные правила
						goto gwlb2;
					}

					break;

				case DEV_FDD_A16M:
					if (GetAltProMode() == ALTPRO_A16M_BASIC_MODE)
					{
						//ещё одно исключение. в режиме бейсика m_nBKPortsIOArea == BK_PURE_PORTSIO_AREA целы 0600 байтов ещё
						if ((addr < BK_PURE_PORTSIO_AREA) && (GetAltProExtCode() & 010)) //если бейсик подключен
						{
							goto gwlb1;
						}
					}
					else if (GetAltProMode() == ALTPRO_A16M_START_MODE)
					{
						//в режиме Start в этом диапазоне можно читать
gwlb1:

						if (OnGetSystemRegister(num, &v, false))
						{
							return v | *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
						}
						else
						{
							return *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
						}
					}

				//иначе, как в обычном режиме, смотрим порты
				default:

					//в обычном режиме тут только регистры могут быть
					if (OnGetSystemRegister(addr, &v, false))
					{
						return v;
					}
					else
					{
						throw CExceptionHalt(addr, _T("Can't read this address."));
					}

					break;
			}
		}
		else
		{
			//по адресам 170000-177000 для SMK если режим не SYS - всегда ОЗУ
			if (GetAltProMode() != ALTPRO_A16M_START_MODE && GetFDDType() == DEV_FDD_SMK512)
			{
				return *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
			}
		}
	}

gwlb2:

	//теперь проверим на чтение из несуществующего банка
	if (m_MemoryMap[nBank].bReadable)
	{
		//если читать можно, заодно и ремап сделаем, может не понадобится переопределять везде эту функцию
		return *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't read this address."));
	}
}


void CMotherBoard::GetByte(WORD addr, BYTE *pValue)
{
	*pValue = GetByte(addr);
}


void CMotherBoard::GetWord(WORD addr, WORD *pValue)
{
	*pValue = GetWord(addr);
}


void CMotherBoard::SetByte(WORD addr, BYTE value)
{
	register int nBank = (addr >> 12) & 0x0f;

	// Сперва проверим, на системные регистры
	if (nBank == 15)
	{
		if (addr >= m_nBKPortsIOArea)
		{
			switch (GetFDDType())
			{
				case DEV_FDD_SMK512:

					//в режиме Hlt11 и Hlt10 в этом диапазоне можно писать, а для старой версии, ещё и в режиме ОЗУ10
					if (GetAltProMode() == ALTPRO_SMK_HLT11_MODE || GetAltProMode() == ALTPRO_SMK_HLT10_MODE /*|| GetAltProMode() == ALTPRO_SMK_OZU10_MODE*/)
					{
						goto sblb1;
					}
					else
					{
						if (OnSetSystemRegister(addr, value, true))
						{
							return;
						}

						goto sblb2;
					}

					break;

				case DEV_FDD_A16M:

					//в режиме Hlt11 в этом диапазоне можно писать
					if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE)
					{
sblb1:
						OnSetSystemRegister(addr, value, true);
						m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
						return;
					}

				//иначе, как в обычном режиме, смотрим порты
				default:

					//в обычном режиме тут только регистры могут быть
					if (OnSetSystemRegister(addr, value, true))
					{
						return;
					}
					else
					{
						throw CExceptionHalt(addr, _T("Can't write this address."));
					}

					break;
			}
		}
		else
		{
			if (GetAltProMode() != ALTPRO_A16M_START_MODE && GetFDDType() == DEV_FDD_SMK512)
			{
				m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
				return;
			}
		}
	}

sblb2:

	if (m_MemoryMap[nBank].bWritable)
	{
		m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
		//если это у нас экран, то передадим в эмулятор экрана данные
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't write this address."));
	}
}


void CMotherBoard::SetWord(WORD addr, WORD value)
{
	register int nBank = (addr >> 12) & 0x0f;
	register WORD num = addr; //специально для регистров hdd smk512
	addr &= 0177776;

	// Сперва проверим, на системные регистры
	if (nBank == 15)
	{
		if (addr >= m_nBKPortsIOArea)
		{
			switch (GetFDDType())
			{
				case DEV_FDD_SMK512:

					//в режиме Hlt11 и Hlt10 в этом диапазоне можно писать, а для старой версии, ещё и в режиме ОЗУ10
					if (GetAltProMode() == ALTPRO_SMK_HLT11_MODE || GetAltProMode() == ALTPRO_SMK_HLT10_MODE /*|| GetAltProMode() == ALTPRO_SMK_OZU10_MODE*/)
					{
						goto swlb1;
					}
					else
					{
						if (OnSetSystemRegister(num, value, false))
						{
							return;
						}

						goto swlb2;
					}

					break;

				case DEV_FDD_A16M:

					//в режиме Hlt11 в этом диапазоне можно писать
					if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE)
					{
swlb1:
						OnSetSystemRegister(num, value, false);
						*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
						return;
					}

				//иначе, как в обычном режиме, смотрим порты
				default:

					//в обычном режиме тут только регистры могут быть
					if (OnSetSystemRegister(addr, value, false))
					{
						return;
					}
					else
					{
						throw CExceptionHalt(addr, _T("Can't write this address."));
					}

					break;
			}
		}
		else
		{
			if (GetAltProMode() != ALTPRO_A16M_START_MODE && GetFDDType() == DEV_FDD_SMK512)
			{
				*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
				return;
			}
		}
	}

swlb2:

	if (m_MemoryMap[nBank].bWritable)
	{
		*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
	}
	else
	{
		throw CExceptionHalt(addr, _T("Can't write this address."));
	}
}


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

BYTE CMotherBoard::GetByteIndirect(WORD addr)
{
	register int nBank = (addr >> 12) & 0x0f;
	register BYTE v;

	if (OnGetSystemRegisterIndirect(addr, &v, true))
	{
		return (GetAltProMode() == ALTPRO_A16M_START_MODE) ? (v | m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)]) : v;
	}

	if ((0177700 <= addr) && (addr < 0177714))
	{
		register WORD w = m_cpu.GetSysRegs(addr);

		if (GetAltProMode() == ALTPRO_A16M_START_MODE)
		{
			w |= m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
		}

		return (addr & 1) ? HIBYTE(w) : LOBYTE(w);
	}

	return m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)];
}


WORD CMotherBoard::GetWordIndirect(WORD addr)
{
	register int nBank = (addr >> 12) & 0x0f;
	addr &= 0177776;
	register WORD v;

	if (OnGetSystemRegisterIndirect(addr, &v, false))
	{
		return (GetAltProMode() == ALTPRO_A16M_START_MODE) ? (v | *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)]) : v;
	}

	if ((0177700 <= addr) && (addr < 0177714))
	{
		register WORD w = m_cpu.GetSysRegs(addr);

		if (GetAltProMode() == ALTPRO_A16M_START_MODE)
		{
			w |= *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)];
		}

		return w;
	}

	return *(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)];
}


void CMotherBoard::SetByteIndirect(WORD addr, BYTE value)
{
	register int nBank = (addr >> 12) & 0x0f;

	if (OnSetSystemRegisterIndirect(addr, value, true))
	{
		if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE || (GetAltProMode() == ALTPRO_SMK_HLT10_MODE && GetFDDType() == DEV_FDD_SMK512))
		{
			m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
		}

		return;
	}

	if ((0177700 <= addr) && (addr < 0177714))
	{
		m_cpu.SetSysRegsInternal(addr, value);

		if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE || (GetAltProMode() == ALTPRO_SMK_HLT10_MODE && GetFDDType() == DEV_FDD_SMK512))
		{
			m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
		}
	}
	else
	{
		m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007777)] = value;
	}
}


void CMotherBoard::SetWordIndirect(WORD addr, WORD value)
{
	register int nBank = (addr >> 12) & 0x0f;
	addr &= 0177776;

	if (OnSetSystemRegisterIndirect(addr, value, false))
	{
		if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE || (GetAltProMode() == ALTPRO_SMK_HLT10_MODE && GetFDDType() == DEV_FDD_SMK512))
		{
			*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)] = value;
		}

		return;
	}

	if ((0177700 <= addr) && (addr < 0177714))
	{
		m_cpu.SetSysRegsInternal(addr, value);

		if (GetAltProMode() == ALTPRO_A16M_HLT11_MODE || (GetAltProMode() == ALTPRO_SMK_HLT10_MODE && GetFDDType() == DEV_FDD_SMK512))
		{
			*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)] = value;
		}
	}
	else
	{
		*(WORD *)&m_pMemory[m_MemoryMap[nBank].nOffset + (addr & 007776)] = value;
	}
}


//это ресет оборудования, выполняемый по команде RESET. вызывается из CPU
void CMotherBoard::ResetDevices()
{
	m_reg177660 = 0100; //команда RESET запрещает прерывания от клавиатуры. проверено.
	m_reg177714in = 0;
	m_reg177714out = 0;
	m_reg177716out_tap = 0200;
	m_reg177716in |= 0300;

	//если у нас есть сопр. по команде RESET он тоже должен инициализироваться (в правильной схеме подключения)
	if (m_pAY8910)
	{
		m_pAY8910->Reset();
	}
}


bool CMotherBoard::ResetHot(WORD addrStart)
{
	if (addrStart == 0)
	{
		addrStart = m_nStartAddr;    // Используем адрес по умолчанию
	}
	else
	{
		m_nStartAddr = addrStart;    // иначе используем адрес из конфигурации
	}

	//инициализация регистров
	m_reg177716in = addrStart & 0177400;
	m_reg177662out = 047400;
	m_reg177664 = 01330;
	m_reg177662in = 0;

	//инициализация карты памяти
	if (!InitMemoryModules())
	{
		return false;
	}

	//инициализация ОЗУ и CPU
	Reset();

	if (m_pSpeaker)
	{
		m_pSpeaker->Reset();
	}

	if (m_pCovox)
	{
		m_pCovox->Reset();
	}

	return true;
}


//////////////////////////////////////////////////////////////////////
//на входе - маска, по которой сбрасываем биты адреса.
void CMotherBoard::ResetCold(WORD addrMask)
{
	m_reg177716in = (m_nStartAddr & ~addrMask) & 0177400;
	m_reg177662out = 047400;
	m_reg177664 = 01330;
	Reset();
	m_reg177716in |= (m_nStartAddr & 0177400); //а в регистр всё-таки занесём стандартное значение, без сброшенных битов
}


void CMotherBoard::StopInterrupt()
{
	//нажали на кнопку стоп
	if (!m_bBlockStop) //если кнопка СТОП не заблокирована
	{
		m_cpu.SetIRQ1();
	}
}

void CMotherBoard::UnStopInterrupt()
{
	//отжали кнопку стоп
	if (!m_bBlockStop) //если кнопка СТОП не заблокирована
	{
		m_cpu.UnsetIRQ1();
	}
}


void CMotherBoard::KeyboardInterrupt(WORD interrupt)
{
	if ((m_reg177660 & 0100) == 0)
	{
		//если прерывания разрешены, делаем прерывание
		m_cpu.InterruptVIRQ(interrupt);
	}
}


void CMotherBoard::AccelerateCPU()
{
	if (m_nCPUFreq < m_nHighBound)
	{
		m_nCPUFreq += SPEED_STEP;
	}
}
void CMotherBoard::AccelerateCPUSmall()
{
	if (m_nCPUFreq < m_nHighBound)
	{
		m_nCPUFreq += SPEED_STEP_SMALL;
	}
}

void CMotherBoard::SlowdownCPU()
{
	if (m_nCPUFreq > m_nLowBound)
	{
		m_nCPUFreq -= SPEED_STEP;
	}
}
void CMotherBoard::SlowdownCPUSmall()
{
	if (m_nCPUFreq > m_nLowBound)
	{
		m_nCPUFreq -= SPEED_STEP_SMALL;
	}
}

/*
Чтение из системных регистров
вход: num - адрес регистра (177660, 177716 и т.п.)
pDst - адрес в массиве, куда сохраняется новое значение
bByteOperation - флаг операции true - байтовая, false - словная
*/
bool CMotherBoard::OnGetSystemRegister(WORD num, void *pDst, bool bByteOperation)
{
	WORD *pDst_W = reinterpret_cast<WORD *>(pDst);
	BYTE *pDst_B = reinterpret_cast<BYTE *>(pDst);
	register bool bRet = true;
	register WORD v = 0;

	switch (num & 0177776)
	{
		case 0177660:
			v = m_reg177660;
			break;

		case 0177662:
			//если читаем из регистра данных клавиатуры,
			//сбросим бит готовности из регистра состояния клавиатуры
			m_reg177660 &= ~0200;
			v = m_reg177662in;
			break;

		case 0177664:
			v = m_reg177664;
			break;

		case 0177714:
			v = m_reg177714in;
			break;

		case 0177716:
			v = m_reg177716in;
			m_reg177716in &= ~4;
			break;

		default:
			bRet = false;
	}

	if (bByteOperation)
	{
		*pDst_B = (num & 1) ? HIBYTE(v) : LOBYTE(v);
	}
	else
	{
		*pDst_W = v;
	}

	return bRet;
}

/*
Запись данных в системные регистры БК0010
    вход: num - адрес регистра(177660, 177716 и т.п.)
          src - записываемое значение.
          bByteOperation - флаг операции true - байтовая, false - словная
*/
bool CMotherBoard::OnSetSystemRegister(WORD num, WORD src, bool bByteOperation)
{
	register WORD mask = 0177777; //это какие биты в регистре используются

	switch (num & 0177776)
	{
		case 0177660:
			/*
			177660
			Регистр состояния клавиатуры.
			(0100)бит 6 -- маскирование прерывание от клавиатуры,
			    "0" -- разрешено прерывание по вектору 060 либо 0274.
			    Прерывание вызывается при появлении "1" в бите 7. Начальное состояние: "0".
			    Доступен по чтению и по записи
			(0200)бит 7 -- готовность: "1" -- в регистре данных клавиатуры(177662) готов код нажатой клавиши.
			    Устанавливается при нажатии на клавишу, сбрасывается при чтении регистра данных клавиатуры.
			    Начальное состояние: "1".
			    Доступен только по чтению.
			другие биты: "0". Доступны только по чтению.
			*/
			mask = 0100;

			if (bByteOperation)
			{
				src &= 0377;

				if (num & 1)
				{
					src = (src << 8) | (m_reg177660 & 0377); // работаем со старшим байтом, младший оставляем неизменным
				}
				else
				{
					src = src | (m_reg177660 & 0177400); // работаем с младшим байтом, старший оставляем неизменным
				}
			}

			//сбрасываем используемые биты и устанавливаем их значения из слова, которое записываем.
			//остальные биты - которые не используются - остаются нетронутыми.
			m_reg177660 = (~mask & m_reg177660) | (mask & src);
			return true;

		case 0177662:
			/*
			177662
			Регистр данных клавиатуры.
			    биты 0-6: код клавиши. Доступ только по чтению.
			(040000)бит 14: разрешение прерывания по(внешнему) таймеру(50 Гц),
			    "0" -- прерывание разрешено,
			    "1" -- таймер отключен.
			    Доступен только по записи.
			    Бит 14 на БК0010 не документирован, и таймер работал только на некоторых экземплярах, можно считать, что его нет
			*/
			return false; //на бк10 запись запрещена. это был один из способов определения бк10 или бк11

		case 0177664:
			/*
			177664
			Регистр скроллинга. Доступен по записи и чтению.
			    биты 0-7: смещение скроллинга, строк. Начальное значение -- 0330.
			(01000)бит 9: сокращённый режим экрана, "0" -- сокращённый(1/4 экрана, старшие адреса),
			    "1" -- полный экран 256 строк.
			*/
			mask = 01377;

			if (bByteOperation)
			{
				src &= 0377;
				/*
				оказалось, что в этот регистр не работает байтовая запись.
				чтение байта работает, но записывается всегда слово.
				а при байтовой операции, старший байт просто теряется.
				*/
			}

			m_reg177664 = (~mask & m_reg177664) | (mask & src);
			return true;

		case 0177714:

			/*177714
			Регистр параллельного программируемого порта ввода вывода - два регистра, входной по чтению и выходной по записи.
			из выходного нельзя ничего прочитать т.к., читается оно из входного,
			во входной невозможно ничего записать, т.к. записывается оно в выходной.
			*/
			if (bByteOperation)
			{
				src &= 0377; // работаем с младшим байтом

				if (num & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			m_reg177714out = src;
			m_pCovox->SetSample(m_reg177714out);
			return true;

		case 0177716:

			/*
			177716
			Системный регистр. Внешний регистр 1(ВР1, SEL1) процессора ВМ1, регистр начального пуска.
			как и 177714 состоит из двух регистров, раздельных по чтению и по записи
			По чтению:
			(004)бит 2: признак записи в системный регистр. Устанавливается в "1" при
			    любой записи в регистр, сбрасывается в "0" после чтения из регистра.
			(020)бит 4: данные с ТЛГ-линии, "0" - логический 0, "1" - логическая 1
			(040)бит 5: данные с магнитофона, "0" - логический 0, "1" - логическая 1
			(100)бит 6: индикатор нажатия клавиши, установлен в "0" если нажата клавиша клавиатуры, "1" если нет нажатых клавиш.
			(200)бит 7: сигнал готовности с ТЛГ-линии, "0" - логический 0, "1" - логическая 1
			        Однако по заводской документации на БК0010 этот бит - константа "1", указывающая на отсутствие
			        в системе команд расширенной арифметики
			    биты 8-15: адрес начального пуска, 100000(БК-0010), младший байт при этом игнорируется
			    биты 0,1,3, не используются, "0".
			По записи:
			(020)бит 4: данные для передачи на ТЛГ-линию, "0" - логический 0, "1" - логическая 1
			(040)бит 5: данные на магнитофон(либо сигнал готовности на ТЛГ-линию - этого в тех документации нету.). Начальное состояние "0".
			(100)бит 6: данные на магнитофон и на пьезодинамик. Начальное состояние "0".
			(200)бит 7: включение двигателя магнитофона, "1" -- стоп, "0" -- пуск. Начальное состояние "1".
			    биты 0,1,3, не используются, "0".
			*/
			if (bByteOperation)
			{
				src &= 0377; // работаем с младшим байтом

				if (num & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			m_reg177716out_tap = src;
			m_pSpeaker->SetSample(m_reg177716out_tap);
			//В БК 2й разряд SEL1 фиксирует любую запись в этот регистр, взводя триггер D9.1 на неограниченное время, сбрасывается который любым чтением этого регистра.
			m_reg177716in |= 4;
			return true;
	}

	return false;
}

/*
Отладочное Чтение из системных регистров
вход: num - адрес регистра (177660, 177716 и т.п.)
pDst - адрес в массиве, куда сохраняется новое значение
bByteOperation - флаг операции true - байтовая, false - словная
*/
bool CMotherBoard::OnGetSystemRegisterIndirect(WORD num, void *pDst, bool bByteOperation)
{
	WORD *pDst_W = reinterpret_cast<WORD *>(pDst);
	BYTE *pDst_B = reinterpret_cast<BYTE *>(pDst);
	register bool bRet = true;
	register WORD v = 0;

	switch (num & 0177776)
	{
		case 0177660:
			v = m_reg177660;
			break;

		case 0177662:
			v = m_reg177662in;
			break;

		case 0177664:
			v = m_reg177664;
			break;

		case 0177714:
			v = m_reg177714in;
			break;

		case 0177716:
			v = m_reg177716in;
			break;

		default:
			bRet = false;
	}

	if (bByteOperation)
	{
		*pDst_B = (num & 1) ? HIBYTE(v) : LOBYTE(v);
	}
	else
	{
		*pDst_W = v;
	}

	return bRet;
}

/*
Отладочная Запись данных в системные регистры БК0010
вход: num - адрес регистра(177660, 177716 и т.п.)
src - записываемое значение.
bByteOperation - флаг операции true - байтовая, false - словная
*/
bool CMotherBoard::OnSetSystemRegisterIndirect(WORD num, WORD src, bool bByteOperation)
{
	register WORD mask = 0177777; //это какие биты в регистре используются

	switch (num & 0177776)
	{
		case 0177660:
			mask = 0100;

			if (bByteOperation)
			{
				src &= 0377;

				if (num & 1)
				{
					src = (src << 8) | (m_reg177660 & 0377); // работаем со старшим байтом, младший оставляем неизменным
				}
				else
				{
					src = src | (m_reg177660 & 0177400); // работаем с младшим байтом, старший оставляем неизменным
				}
			}

			//сбрасываем используемые биты и устанавливаем их значения из слова, которое записываем.
			//остальные биты - которые не используются - остаются нетронутыми.
			m_reg177660 = (~mask & m_reg177660) | (mask & src);
			return true;

		case 0177662:
			return false; //на бк10 запись запрещена. это был один из способов определения бк10 или бк11

		case 0177664:
			mask = 01377;

			if (bByteOperation)
			{
				src &= 0377;
			}

			m_reg177664 = (~mask & m_reg177664) | (mask & src);
			return true;

		case 0177714:
			if (bByteOperation)
			{
				src &= 0377; // работаем с младшим байтом

				if (num & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			m_reg177714out = src;
			m_pCovox->SetSample(m_reg177714out);
			return true;

		case 0177716:
			if (bByteOperation)
			{
				src &= 0377; // работаем с младшим байтом

				if (num & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			m_reg177716out_tap = src;
			m_pSpeaker->SetSample(m_reg177716out_tap);
			return true;
	}

	return false;
}


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


void CMotherBoard::RunOver()
{
	// Run one command with go over command
	register WORD pc = GetRON(CCPU::R_PC);
	register WORD instr = GetWordIndirect(pc);
	register WORD NextAddr = 0;

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

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


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



void CMotherBoard::BreakCPU()
{
	m_lockBreaked.Lock(); //включаем отладочную приостановку
	CDebugger::InitOutMode();

	if (m_pParent)
	{
		m_pParent->SendMessage(WM_CPU_DEBUGBREAK);
	}
}

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


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

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


void CMotherBoard::StopCPU(bool bUnbreak)
{
	m_nGotoAddress = ADDRESS_NONE;
	m_lockRunning.UnLock();

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

	Sleep(50); //подождём остановки фрейма, правда это не помогает
}

void CMotherBoard::NextTick()
{
	ASSERT(m_pSpeaker != nullptr);

	//обработка медиатактов
	if (m_nMediaTickCount == 0)
	{
		// Обнуляем звуковые буферы
		m_pSpeaker->Reset();
		// Получаем сохранённые 1/CPU_FRAMERATE секунды звуковые данные
		ZeroMemory(m_pSoundBuffer, m_nSoundBufferLen);
		// Берём данные с ленты -> m_pSoundBuffer
		m_pParent->GetTapePtr()->PlayWaveGetBuffer(m_pSoundBuffer, m_nSoundBufferLen);
		// Посылаем данные с ленты в 177716 <- m_pSoundBuffer
		m_pSpeaker->ReceiveSamples(m_pSoundBuffer, m_nSoundBufferLen);
		m_nBufPos = 0;
	}

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

	// Берём звуки со спикера BK -> m_pSoundBuffer
	if (m_pSpeaker->IsSoundEnabled())
	{
		if (m_pSpeaker->GetSample(fL, fR))
		{
			m_reg177716in |= 040;
		}
		else
		{
			m_reg177716in &= ~040;
		}

		m_pSound->SoundGen_MixSample(fL, fR); //микшируем сэмплы внутри m_pSound
	}

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

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

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

	if (++m_nMediaTickCount >= m_nMediaTicksPerFrame)
	{
		m_nMediaTickCount = 0;
		// Отправляем их на ленту <- m_pSoundBuffer
		m_pParent->GetTapePtr()->RecordWaveGetBuffer(m_pSoundBuffer, m_nSoundBufferLen);
		// Отображаем буфер на осциллографе
		m_pParent->GetOscillatorViewPtr()->RedrawWindow();  //эта штука иногда начинает вызывать дедлоки или мемлики.
		//а иногда - нет, причины неясны.
		//m_pParent->GetOscillatorViewPtr()->Display(); //а эта - как правило нет.
	}

	CDevice::NextTick();
}

void CMotherBoard::ExecuteFrame()
{
	m_lockExecuteFrame.Lock();

	if (!IsCPUBreaked())
	{
		m_fMemory_Mod = (double)m_nCPUFreq / 750000.0;      //коэффициент пересчёта ТЧ проца в количество обращений к видеопамяти за фрейм
		m_fTickCount += (double)m_nCPUFreq / CPU_FRAMERATE; //количество тактов процессора в текущем фрейме. При нестандартной частоте
		//может быть совсем не целое число.
		m_fFDD_Mod = m_fTickCount / (15625.0 / CPU_FRAMERATE);  //15625 = 5 оборотов в сек, в одном обороте 3125 слов,
		//норма 15625.0, больше - быстрее вращение, меньше - медленнее
		m_fMedia_Mod = m_fTickCount / (double)m_nMediaTicksPerFrame; //коэффициент пересчёта тактов процессора в медиа такты
		//означает - через сколько тактов процессора надо вставлять 1 медиа такт, т.к. число не целое, то
		//получается приблизительно.
	}

	double fCpuTickTime = 1.0 / m_nCPUFreq; //длительность одного такта в секундах

	while (--m_fTickCount > 0.0)  //пока такты укладываются в фрейм
	{
		// Если время пришло, выполняем текущую инструкцию
		if (--m_nCPUTicks <= 0)
		{
			// Если останов на адресе где стоит отладочный курсор
			if (m_nGotoAddress == m_nPreviousPC)
			{
				BreakCPU();
			}

			while (IsCPUBreaked()) //пока процессор приостановлен - ждём
			{
				if (!IsCPURun())    //если решили всё остановить,
				{
					m_nCPUTicks++;  //инкрементируем счётчики обратно
					m_fTickCount++;
					m_lockExecuteFrame.UnLock();
					return;         //и выходим из функции
				}

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

			try
			{
				m_nCPUTicks = m_cpu.TranslateInstruction(); // Выполняем одну инструкцию, возвращаем время выполнения одной инструкции в тактах.
				Interception(); //перехват разных подпрограмм монитора, для их эмуляции
			}
			catch (CExceptionHalt &halt)
			{
				// BK Violation exception. Например запись в ПЗУ или чтение из несуществующей памяти
				if (m_bAskForBreak)
				{
					BreakCPU(); // Останавливаем CPU для отладки
					register WORD pc = GetPrevPC();
					CString strMessage;
					strMessage.Format(IDS_ERRMSG_ROM, halt.m_addr, pc, halt.m_info);
					CBKMessageBox message;
					register int answer = message.Show(strMessage, MB_ABORTRETRYIGNORE | MB_DEFBUTTON2 | MB_ICONSTOP);

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

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

						case IDABORT: //если выбрали "прервать" - остаёмся в отладочном останове
						default:
							break;
					}
				}
				else
				{
					m_cpu.ReplyError(); // Делаем прер. по вектору 4(halt) в следующем цикле
				}
			}
			catch (...)
			{
				// Любое другое исключение. Неизвестное исключение или ошибка доступа к памяти WINDOWS.
				// Эта ошибка может быть неизвестной ошибкой BK или ошибкой эмулятора
				register WORD pc = GetPrevPC();
				CString strMessage;
				strMessage.LoadString(IDS_ERRMSG_INTERNAL);
				CBKMessageBox(strMessage, MB_OK | MB_ICONSTOP);
				BreakCPU();
				StopCPU(false);
				break; // Прекращаем выполнять инструкции
			}

			// Сохраняем текущее значение PC для отладки
			m_nPreviousPC = m_cpu.GetRON(CCPU::R_PC);

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

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

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

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

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

	m_lockExecuteFrame.UnLock();
}


//////////////////////////////////////////////////////////////////////
//перехват разных подпрограмм монитора, для их эмуляции
bool CMotherBoard::Interception()
{
	switch (GetRON(CCPU::R_PC) & 0177776)
	{
		case 0116076:

			//если по этому адресу действительно ПЗУ монитора БК10
			if ((GetWordIndirect(0116076) == 04467) && (GetWordIndirect(0116100) == 0172244))
			{
				if (m_pParent)
				{
					m_pParent->SendMessage(WM_TAPE_FILE_LOAD);
				}
			}

			return true; //если выполнилось
	}

	return false; //если ничего не выполнилось
}


bool CMotherBoard::LoadRomModule(int iniRomNameIndex, int bank)
{
	CString strPath = g_Config.m_strROMPath +
	                  g_Config.GetIniObj()->GetValueString(IDS_INI_SECTIONNAME_ROMMODULES, iniRomNameIndex, _T(""));
	CFile file;

	if (file.Open(strPath, CFile::modeRead))
	{
		register UINT len = (UINT)file.GetLength();

		if (len > 020000) //размер ПЗУ не должен быть больше 8кб
		{
			len = 020000;
		}

		UINT readed = file.Read(&m_pMemory[bank << 12], len);
		file.Close();

		if (readed == len)
		{
			return true;
		}
	}
	else
	{
		CString strError;
		strError.LoadString(IDS_ERROR_CANTOPENFILE);
		CBKMessageBox(strError + _T('\'') + strPath + _T('\''), MB_OK | MB_ICONSTOP);
	}

	return false;
}


bool CMotherBoard::InitMemoryModules()
{
	register bool bRes = true;
	bRes = bRes && LoadRomModule(IDS_INI_BK10_RE2_017_MONITOR, BRD_10_MON10_BNK);
	bRes = bRes && LoadRomModule(IDS_INI_BK10_RE2_106_BASIC1, BRD_10_BASIC10_1_BNK);
	bRes = bRes && LoadRomModule(IDS_INI_BK10_RE2_107_BASIC2, BRD_10_BASIC10_2_BNK);
	bRes = bRes && LoadRomModule(IDS_INI_BK10_RE2_108_BASIC3, BRD_10_REGISTERS_BNK);

	//и проинициализируем карту памяти
	if (bRes)
	{
		MemoryManager();
	}

	return bRes;
}

void CMotherBoard::MemoryManager()
{
	for (int i = 0; i <= 017; ++i)
	{
		m_MemoryMap[i].bReadable = true;
		m_MemoryMap[i].bWritable = true;
		m_MemoryMap[i].nBank = i;
		m_MemoryMap[i].nPage = i >> 2;
		m_MemoryMap[i].nOffset = i << 12;
	}

	for (int i = 010; i <= 017; ++i)
	{
		m_MemoryMap[i].bWritable = false;
	}

	//m_MemoryMap[017].bReadable = false; //170000-177600 - всё ещё бейсик, а для МСТД там ПЗУ тестов
}


bool CMotherBoard::RestoreState(CMSFManager &msf, HBITMAP hScreenshot)
{
	if (!RestorePreview(msf, hScreenshot))
	{
		return false;
	}

	if (!RestoreConfig(msf))
	{
		return false;
	}

	if (!RestoreRegisters(msf))
	{
		return false;
	}

	if (!RestoreMemoryMap(msf)) //необходимо делать строго перед работой с памятью.
	{
		return false;
	}

	if (!RestoreMemory(msf))
	{
		return false;
	}

	return true;
}

bool CMotherBoard::RestoreConfig(CMSFManager &msf)
{
	if (msf.IsLoad())
	{
		msf.GetBlockConfig();
		MSF_FRAMEDATA framedata;

		if (msf.GetBlockFrameData(&framedata))
		{
			m_nCPUTicks     = framedata.nCPUTicks;
			m_fTickCount    = framedata.fTickCount;
			m_fMediaTicks   = framedata.fMediaTicks;
			m_fMedia_Mod    = framedata.fMedia_Mod;
			m_fMemoryTicks  = framedata.fMemoryTicks;
			m_fMemory_Mod   = framedata.fMemory_Mod;
			m_fFDDTicks     = framedata.fFDDTicks;
			m_fFDD_Mod      = framedata.fFDD_Mod;
			m_cpu.SetTimerSpeedInternal(framedata.nTimerSpeed);
			m_pScreenBufferAddress  = m_pMemory + framedata.nScreenBufferAddressOffset;
			m_nScreenCycleCount     = framedata.nScreenCycleCount;
			m_nScreenLinesCount     = framedata.nScreenLinesCount;
			m_nScreenLineBytesCount = framedata.nScreenLineBytesCount;
			m_nScreenStage          = framedata.nScreenStage;
			m_nScreenLinesStage     = framedata.nScreenLinesStage;
			m_nScreenLineCycleCount = framedata.nScreenLineCycleCount;
		}
		else
		{
			m_nCPUTicks =    0;
			m_fTickCount =   0.0;
			m_fMediaTicks =  0.0;
			m_fMemoryTicks = 0.0;
			m_fFDDTicks =    0.0;
		}

		return true; //если конфиг не прочёлся, значит этого блока просто нет. ничего страшного
	}
	else
	{
		bool bRet = msf.SetBlockConfig();
		MSF_FRAMEDATA framedata;
		framedata.nCPUTicks = m_nCPUTicks;
		framedata.fTickCount = m_fTickCount;
		framedata.fMediaTicks = m_fMediaTicks;
		framedata.fMedia_Mod = m_fMedia_Mod;
		framedata.fMemoryTicks = m_fMemoryTicks;
		framedata.fMemory_Mod = m_fMemory_Mod;
		framedata.fFDDTicks = m_fFDDTicks;
		framedata.fFDD_Mod = m_fFDD_Mod;
		framedata.nTimerSpeed = m_cpu.GetTimerSpeedInternal();
		framedata.nScreenBufferAddressOffset = DWORD(m_pScreenBufferAddress - m_pMemory);
		framedata.nScreenCycleCount = m_nScreenCycleCount;
		framedata.nScreenLinesCount = m_nScreenLinesCount;
		framedata.nScreenLineBytesCount = m_nScreenLineBytesCount;
		framedata.nScreenStage = m_nScreenStage;
		framedata.nScreenLinesStage = m_nScreenLinesStage;
		framedata.nScreenLineCycleCount = m_nScreenLineCycleCount;
		return  bRet && msf.SetBlockFrameData(&framedata);
	}
}


bool CMotherBoard::RestoreRegisters(CMSFManager &msf)
{
	MSF_CPU_REGISTERS cpu_reg;
	MSF_PORT_REGS port_regs;

	if (msf.IsLoad())
	{
		if (!msf.GetBlockCPURegisters(&cpu_reg))
		{
			return false;
		}

		SetRON(CCPU::R_R0, cpu_reg.r0);
		SetRON(CCPU::R_R1, cpu_reg.r1);
		SetRON(CCPU::R_R2, cpu_reg.r2);
		SetRON(CCPU::R_R3, cpu_reg.r3);
		SetRON(CCPU::R_R4, cpu_reg.r4);
		SetRON(CCPU::R_R5, cpu_reg.r5);
		SetRON(CCPU::R_SP, cpu_reg.sp);
		SetRON(CCPU::R_PC, cpu_reg.pc);
		SetRON(CCPU::R_PSW, cpu_reg.psw);

		if (msf.GetVersion() >= MSF_VERSION_MINIMAL)
		{
			if (!msf.GetBlockPortRegs(&port_regs))
			{
				return false;
			}

			m_reg177660 = port_regs.p0177660;
			m_reg177662in = port_regs.p0177662_in;
			m_reg177662out = port_regs.p0177662_out;
			m_reg177664 = port_regs.p0177664;
			m_reg177714in = port_regs.p0177714_in;
			m_reg177714out = port_regs.p0177714_out;
			m_reg177716in = port_regs.p0177716_in;
			m_reg177716out_tap = port_regs.p0177716_out_tap;
			m_reg177716out_mem = port_regs.p0177716_out_mem;
			m_cpu.SetSysRegsInternal(0177700, port_regs.p0177700);
			m_cpu.SetSysRegsInternal(0177702, port_regs.p0177702);
			m_cpu.SetSysRegsInternal(0177704, port_regs.p0177704);
			m_cpu.SetSysRegsInternal(0177706, port_regs.p0177706);
			m_cpu.SetSysRegsInternal(0177710, port_regs.p0177710);
			m_cpu.SetSysRegsInternal(0177712, port_regs.p0177712);
		}
		else
		{
			return false;
		}
	}
	else
	{
		cpu_reg.r0 = GetRON(CCPU::R_R0);
		cpu_reg.r1 = GetRON(CCPU::R_R1);
		cpu_reg.r2 = GetRON(CCPU::R_R2);
		cpu_reg.r3 = GetRON(CCPU::R_R3);
		cpu_reg.r4 = GetRON(CCPU::R_R4);
		cpu_reg.r5 = GetRON(CCPU::R_R5);
		cpu_reg.sp = GetRON(CCPU::R_SP);
		cpu_reg.pc = GetRON(CCPU::R_PC);
		cpu_reg.psw = GetRON(CCPU::R_PSW);

		if (!msf.SetBlockCPURegisters(&cpu_reg))
		{
			return false;
		}

		port_regs.p0177660 = m_reg177660;
		port_regs.p0177662_in = m_reg177662in;
		port_regs.p0177662_out = m_reg177662out;
		port_regs.p0177664 = m_reg177664;
		port_regs.p0177714_in = m_reg177714in;
		port_regs.p0177714_out = m_reg177714out;
		port_regs.p0177716_in = m_reg177716in;
		port_regs.p0177716_out_tap = m_reg177716out_tap;
		port_regs.p0177716_out_mem = m_reg177716out_mem;
		port_regs.p0177700 = m_cpu.GetSysRegs(0177700);
		port_regs.p0177702 = m_cpu.GetSysRegs(0177702);
		port_regs.p0177704 = m_cpu.GetSysRegs(0177704);
		port_regs.p0177706 = m_cpu.GetSysRegs(0177706);
		port_regs.p0177710 = m_cpu.GetSysRegs(0177710);
		port_regs.p0177712 = m_cpu.GetSysRegs(0177712);

		if (!msf.SetBlockPortRegs(&port_regs))
		{
			return false;
		}
	}

	return true;
}


bool CMotherBoard::RestoreMemory(CMSFManager &msf)
{
	if (msf.IsLoad())
	{
		if (!msf.GetBlockBaseMemory(m_pMemory))
		{
			return false;
		}
	}
	else
	{
		if (!msf.SetBlockBaseMemory(m_pMemory))
		{
			return false;
		}
	}

	return true;
}

bool CMotherBoard::RestoreMemoryMap(CMSFManager &msf)
{
	if (msf.IsLoad())
	{
		if (!msf.GetBlockMemMap(m_MemoryMap, &m_ConfBKModel))
		{
			return false;
		}
	}
	else
	{
		CONFIG_BKMODEL_NUMBER n = g_Config.GetBKModelNumber(); //если неудача, возвратится 0

		if (!msf.SetBlockMemMap(m_MemoryMap, &m_ConfBKModel))
		{
			return false;
		}
	}

	return true;
}


bool CMotherBoard::RestorePreview(CMSFManager &msf, HBITMAP hScreenshot)
{
	bool bRet = true;

	if (msf.IsSave())
	{
		ASSERT(hScreenshot);

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

			if (hBm)
			{
				bRet = msf.SetBlockPreview(hBm);
				DeleteObject(hBm);
			}
		}
	}

	return bRet;
}

/*
Допустим, есть глобальный фрейм g_Frame = 20 мс или 50 Гц
В этот фрейм влазит:
1. видео кадр. (половина от секамовского интерлейса)
1. звуковой фрейм 20 мс или 50 Гц
3. CPU_Clk / g_Frame тактов процессора

Видеокадр. 312.5 строк, 256 строк видимых + 56.5 строк на поля и обратный ход луча
1 строка - 64 мкс. 512 точек. 48 циклов доступа к памяти
512 точек == 32 слова == 32 цикла доступа к памяти.
1 цикл доступа к памяти - 8 тактов 6МГц (считаем сразу 4 на экран + 4 на проц)
312.5 строк == 15000 циклов доступа к памяти

дальше надо как-то согласовать CPU_Clk, 3МГц и формирование экрана.

сперва 50 строк * 48 циклов  ничего не делаем

потом вывод на экран 256 * 48 = 12288 циклов
for (int i = 0; i < 256; i++)
{
    8 циклов ничего не делаем
    32 цикла выводим 32 слова
    8 циклов ничего не делаем
}

потом 6.5 строк * 48 циклов  снова ничего не делаем

Просто надо сделать такую процедуру, как и с аудио фреймом. примерный множитель.
и сколько циклов обработки экрана влазит в такты инструкции проца,
столько и делать.
---------------------------------------------------------------------
Сравнительные эксперименты показали, что 1 строка - 64 мкс. 512 точек.
это не 48 циклов доступа к памяти, а меньше. Либо где-то ошибка.

m_nScreenCycleCount;        //счётчик циклов доступа к памяти
m_nScreenLinesCount;        //счётчик прорисованных строк
m_nScreenLineBytesCount;    //счётчик прорисованных байтов в строке
m_nScreenStage;             //номер этапа: 0 - верхнее поле, 1 - прорисовка экрана, 2 - нижнее поле
m_nScreenLinesStage;        //номер этапа прорисовки строки: 0 - левое поле, 1 - информация (32слова), 2 - правое поле
m_nScreenLineCycleCount;    //счётчик циклов доступа к памяти при прорисовке строки
*/
void CMotherBoard::Make_One_Screen_Cycle()
{
	ASSERT(m_pScreen);
	m_nScreenCycleCount++;

	if (m_nScreenCycleCount == 1 * 48)
	{
		m_pScreen->SetOffset(LOBYTE(m_reg177664)); //смещение фиксируется перед началом вывода информации на экран.
	}
	else if (m_nScreenCycleCount == 298 * 48)
	{
		//по одним сведениям - прерывание по таймеру 50гц происходит в начале прорисовки второй информационной строки
		//по моим экспериментам - прерывание по таймеру 50гц происходит на пару строк после последней информационной строки экрана
		if ((m_reg177662out & 040000) == 0) //если бит 14 установлен, таймер не работает.
		{
			m_cpu.TickIRQ2();
		}
	}

	switch (m_nScreenStage)
	{
		case 0:
		{
			//этап 0 - верхнее поле
			if (m_nScreenCycleCount >= 42 * 48) //if (m_nScreenCycleCount >= 41 * 48)
			{
				m_pScreenBufferAddress = m_pMemory;
				m_nScreenStage = 1;
				m_nScreenLinesCount = 0;
				m_nScreenLinesStage = 0;
				m_nScreenLineCycleCount = 0;
			}

			break;
		}

		case 1:
		{
			//этап 1 - рисуем экран, 256 информационных строк
			m_nScreenLineCycleCount++;

			switch (m_nScreenLinesStage)
			{
				case 0:
				{
					if (m_nScreenLineCycleCount >= 8) //8
					{
						m_nScreenLinesStage = 1;
						m_nScreenLineBytesCount = 0;
					}

					break;
				}

				case 1:
				{
					register DWORD_PTR nScrAddr = ((DWORD_PTR)GetScreenPage()) << 14;
					m_pScreen->SetExtendedMode((m_reg177664 & 01000) != 0);
					m_pScreen->PrepareScreenLineByteRGB32(m_nScreenLinesCount, m_nScreenLineBytesCount++, m_pScreenBufferAddress[nScrAddr]);
					m_pScreenBufferAddress++;
					m_pScreen->PrepareScreenLineByteRGB32(m_nScreenLinesCount, m_nScreenLineBytesCount++, m_pScreenBufferAddress[nScrAddr]);
					m_pScreenBufferAddress++;

					if (m_nScreenLineBytesCount >= 64)
					{
						m_nScreenLinesStage = 2;

						if (++m_nScreenLinesCount >= 256)
						{
							register CView *view = m_pParent->GetActiveView();

							if (view)
							{
								view->Invalidate(false);
							}

							m_nScreenStage = 2;
						}
					}

					break;
				}

				case 2:
				{
					if (m_nScreenLineCycleCount >= 48) //48
					{
						m_nScreenLineCycleCount = 0;
						m_nScreenLinesStage = 0;
					}

					break;
				}
			}

			break;
		}

		case 2:
		{
			//этап 2 - нижнее поле + обратный ход луча
			if (m_nScreenCycleCount >= 15360)
			{
				m_nScreenCycleCount = 0;
				m_nScreenStage = 0;
			}

			break;
		}
	}
}

//специально для отладки, рисуем экран старым методом.
//при этом новый метод не отключается и поэтому возможны разного рода казусы.
void CMotherBoard::DrawDebugScreen()
{
	if (m_pScreen)
	{
		m_pScreen->SetRegister(m_reg177664);
		register DWORD_PTR nScrAddr = ((DWORD_PTR)GetScreenPage()) << 14;
		m_pScreen->PrepareScreenRGB32(m_pMemory + nScrAddr);
		register CView *view = m_pParent->GetActiveView();

		if (view)
		{
			view->Invalidate(false);
		}
	}
}


void CMotherBoard::StopTimerThread()
{
	if (!m_lockKillTimerEvent.IsLocked())
	{
		m_lockKillTimerEvent.Lock();
		MSG msg;

		while (m_lockKillTimerEvent.IsLocked()) //ждём завершения потока
		{
			//берём на себя трансляцию сообщений, чтобы не зависало
			if (GetMessage(&msg, m_pParent->GetSafeHwnd(), 0, 0))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		m_lockKillTimerEvent.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
	}
}

/*
Тут реализуется таймер 50Гц. С помощью высокоточного waitable timerа
*/
bool CMotherBoard::StartTimerThread()
{
	if ((m_pTimerThread = AfxBeginThread(
	                          TimerThreadWrapper,
	                          (void *)this,
	                          THREAD_PRIORITY_NORMAL,
	                          0,
	                          CREATE_SUSPENDED)
	    ) == nullptr)
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
		return false;
	}

	m_pTimerThread->m_bAutoDelete = true;
	m_pTimerThread->ResumeThread();
	return true;
}

UINT AFX_CDECL CMotherBoard::TimerThreadWrapper(LPVOID lpParam)
{
	CMotherBoard *pThis = reinterpret_cast<CMotherBoard *>(lpParam);
	return pThis->TimerThreadFunc();
}

UINT CMotherBoard::TimerThreadFunc()
{
	//QUERY_PERFORMANCE_METHOD в виндовс 10 работает замечательно
	//а вот в винхп хреново, и наоборот
#define QUERY_PERFORMANCE_METHOD 1
//#undef QUERY_PERFORMANCE_METHOD
	//DWORD_PTR oldmask = SetThreadAffinityMask(GetCurrentThread(), 1);
#ifdef QUERY_PERFORMANCE_METHOD
	LARGE_INTEGER i1, i2, li_clock;
	double res, dClk;
	QueryPerformanceFrequency(&li_clock);
	dClk = (double)li_clock.QuadPart;
#else
	LARGE_INTEGER liDueTime;    //интервал таймера
	//liDueTime.QuadPart = -10000000LL / CPU_FRAMERATE;
	liDueTime.QuadPart = -9000000LL / CPU_FRAMERATE; //вот такая пока фигня будет - полное игнорирование реальности.
	/* тут выяснилось, что синхронизация идёт по звуку.
	 * и этот таймер нужен лишь тогда, когда нет звуковой подсистемы.
	 * главное условие, чтобы он выставлял задержку хоть примерно равную фрейму.
	**/
	// создаём waitable timer.
	HANDLE hWaitableTimer = CreateWaitableTimer(nullptr, false, nullptr);

	if (nullptr == hWaitableTimer)
	{
		return -1;
	}

#endif

	do
	{
#ifdef QUERY_PERFORMANCE_METHOD
		QueryPerformanceCounter(&i1);
#else
		// Устанавливаем таймер.
		//устанавливаем таймер каждый раз потому, что период самоустановки таймера
		//измеряется в миллисекундах. а нам то нужны микросекунды -- это старый комментарий.
		// сейчас уже выставляется 20мс. но будем делать как делали, потому что у виндовса свои понятия о длительности миллисекунды.
		SetWaitableTimer(hWaitableTimer, &liDueTime, 0, nullptr, nullptr, false);
#endif

		if (!m_pParent->IsLockStop())
		{
			if (IsCPURun()) //если отладочного останова нет, а процессор остановлен для каких-то целей, значит ничего выполнять не надо
			{
				// Выполняем набор инструкций
				ExecuteFrame();
				// Парсим скрипт, если он есть
				m_pParent->GetScriptRunnerPtr()->RunScript();
			}
		}

#ifdef QUERY_PERFORMANCE_METHOD

		do
		{
			Sleep(1);
			QueryPerformanceCounter(&i2);
			res = (double)(i2.QuadPart - i1.QuadPart) / dClk;
		}
		while (res < 0.020);     //20мс

#else
		// ждём срабатывания таймера.
		WaitForSingleObject(hWaitableTimer, INFINITE);
#endif
	}
	while (!m_lockKillTimerEvent.IsLocked());     //пока не придёт событие остановки

#ifdef QUERY_PERFORMANCE_METHOD
#else
	CancelWaitableTimer(hWaitableTimer);
	CloseHandle(hWaitableTimer);
#endif
	//SetThreadAffinityMask(GetCurrentThread(), oldmask);
	m_lockKillTimerEvent.UnLock();
	return 0;
}

