#include "pch.h"
#include "AZBK.h"

#include "Platform.h"
#include "BK001001.h"

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

IMPLEMENT_DYNAMIC(CAZBK, CBKMPIDevice)

uint32_t CAZBK::m_azPalette[AZ_PALETTE_SIZE] =
{
	// 256 цветов
	0xff000000,0xff080808,0xff101010,0xff181818,0xff202020,0xff282828,0xff303030,0xff383838,0xff404040,0xff484848,0xff505050,0xff585858,0xff606060,0xff686868,0xff707070,0xff787878,
	0xff808080,0xff888888,0xff909090,0xff989898,0xffa0a0a0,0xffa8a8a8,0xffb0b0b0,0xffb8b8b8,0xffc0c0c0,0xffc8c8c8,0xffd0d0d0,0xffd8d8d8,0xffe0e0e0,0xffe8e8e8,0xfff0f0f0,0xfff8f8f8,
	0xff000000,0xff0000f8,0xff00f800,0xff00f8f8,0xfff80000,0xfff800f8,0xfff8f800,0xfff8f8f8,0xff000000,0xff000030,0xff000060,0xff000098,0xff0000c8,0xff0000f8,0xff003000,0xff003030,
	0xff003060,0xff003098,0xff0030c8,0xff0030f8,0xff006000,0xff006030,0xff006060,0xff006098,0xff0060c8,0xff0060f8,0xff009800,0xff009830,0xff009860,0xff009898,0xff0098c8,0xff0098f8,
	0xff00c800,0xff00c830,0xff00c860,0xff00c898,0xff00c8c8,0xff00c8f8,0xff00f800,0xff00f830,0xff00f860,0xff00f898,0xff00f8c8,0xff00f8f8,0xff300000,0xff300030,0xff300060,0xff300098,
	0xff3000c8,0xff3000f8,0xff303000,0xff303030,0xff303060,0xff303098,0xff3030c8,0xff3030f8,0xff306000,0xff306030,0xff306060,0xff306098,0xff3060c8,0xff3060f8,0xff309800,0xff309830,
	0xff309860,0xff309898,0xff3098c8,0xff3098f8,0xff30c800,0xff30c830,0xff30c860,0xff30c898,0xff30c8c8,0xff30c8f8,0xff30f800,0xff30f830,0xff30f860,0xff30f898,0xff30f8c8,0xff30f8f8,
	0xff600000,0xff600030,0xff600060,0xff600098,0xff6000c8,0xff6000f8,0xff603000,0xff603030,0xff603060,0xff603098,0xff6030c8,0xff6030f8,0xff606000,0xff606030,0xff606060,0xff606098,
	0xff6060c8,0xff6060f8,0xff609800,0xff609830,0xff609860,0xff609898,0xff6098c8,0xff6098f8,0xff60c800,0xff60c830,0xff60c860,0xff60c898,0xff60c8c8,0xff60c8f8,0xff60f800,0xff60f830,
	0xff60f860,0xff60f898,0xff60f8c8,0xff60f8f8,0xff980000,0xff980030,0xff980060,0xff980098,0xff9800c8,0xff9800f8,0xff983000,0xff983030,0xff983060,0xff983098,0xff9830c8,0xff9830f8,
	0xff986000,0xff986030,0xff986060,0xff986098,0xff9860c8,0xff9860f8,0xff989800,0xff989830,0xff989860,0xff989898,0xff9898c8,0xff9898f8,0xff98c800,0xff98c830,0xff98c860,0xff98c898,
	0xff98c8c8,0xff98c8f8,0xff98f800,0xff98f830,0xff98f860,0xff98f898,0xff98f8c8,0xff98f8f8,0xffc80000,0xffc80030,0xffc80060,0xffc80098,0xffc800c8,0xffc800f8,0xffc83000,0xffc83030,
	0xffc83060,0xffc83098,0xffc830c8,0xffc830f8,0xffc86000,0xffc86030,0xffc86060,0xffc86098,0xffc860c8,0xffc860f8,0xffc89800,0xffc89830,0xffc89860,0xffc89898,0xffc898c8,0xffc898f8,
	0xffc8c800,0xffc8c830,0xffc8c860,0xffc8c898,0xffc8c8c8,0xffc8c8f8,0xffc8f800,0xffc8f830,0xffc8f860,0xffc8f898,0xffc8f8c8,0xffc8f8f8,0xfff80000,0xfff80030,0xfff80060,0xfff80098,
	0xfff800c8,0xfff800f8,0xfff83000,0xfff83030,0xfff83060,0xfff83098,0xfff830c8,0xfff830f8,0xfff86000,0xfff86030,0xfff86060,0xfff86098,0xfff860c8,0xfff860f8,0xfff89800,0xfff89830,
	0xfff89860,0xfff89898,0xfff898c8,0xfff898f8,0xfff8c800,0xfff8c830,0xfff8c860,0xfff8c898,0xfff8c8c8,0xfff8c8f8,0xfff8f800,0xfff8f830,0xfff8f860,0xfff8f898,0xfff8f8c8,0xfff8f8f8,
	// 4 цвета 16 по 4
	0xff000000,0xff0000f8,0xff00f800,0xfff80000,0xff000000,0xfff8f800,0xfff800f8,0xfff80000,0xff000000,0xff00f8f8,0xff0000f8,0xfff800f8,0xff000000,0xff00f800,0xff00f8f8,0xfff8f800,
	0xff000000,0xfff800f8,0xff00f8f8,0xfff8f8f8,0xff000000,0xfff8f8f8,0xfff8f8f8,0xfff8f8f8,0xff000000,0xffb80000,0xff980000,0xfff80000,0xff000000,0xffb8f800,0xff98f800,0xfff8f800,
	0xff000000,0xffb800f8,0xff9800f8,0xfff800f8,0xff000000,0xff98f800,0xff9800f8,0xff980000,0xff000000,0xffb8f800,0xffb800f8,0xffb80000,0xff000000,0xff00f8f8,0xfff8f800,0xfff80000,
	0xff000000,0xfff80000,0xff00f800,0xff00f8f8,0xff000000,0xff00f8f8,0xfff8f800,0xfff8f8f8,0xff000000,0xfff8f800,0xff00f800,0xfff8f8f8,0xff000000,0xff00f8f8,0xff00f800,0xfff8f8f8,
	// 16 цветов
	0xff000000,0xff0000a8,0xff00a800,0xff00a8a8,0xffa80000,0xffa800a8,0xffa85000,0xffa8a8a8,0xff505050,0xff0000f8,0xff00f800,0xff00f8f8,0xfff80000,0xfff800f8,0xfff8f800,0xfff8f8f8,
	// 2 цвета
	0xff000080,0xfff8f8f8
};


CAZBK::CAZBK()
	: m_pTexture(nullptr)
	, m_pAZBKSound(nullptr)
	, m_bA16M_Trigger(false)
	, m_nAltproMode(0)
	, m_nAltProMemBank(0)
{
	srand((unsigned)time(nullptr));
	g_Config.m_bEnableIRPS = false; // выключаем блок ИРПС, чтобы он своими регистрами не мешался.
	g_Config.m_b2ndAY8910 = true; // второй AY всегда включен.
	m_pAZBKSound = std::make_unique<CBkSound>();

	if (!m_pAZBKSound)
	{
		//TODO
		ASSERT(false);
	}

	m_pAZCtrl = std::make_unique<CAZBKController>();
	if (!m_pAZCtrl)
	{
		//TODO
		ASSERT(false);
	}

	m_pAZBlt = std::make_unique<AZ_Blitter>();
	if (!m_pAZBlt)
	{
		//TODO
		ASSERT(false);
	}

}

CAZBK::~CAZBK()
{
	StopDMAThread();
	Stop50НzThread();
}

bool CAZBK::InitialSetDevice()
{
	SetScreenParam(m_AZRegs.nSCRCSR);
	m_pMemoryMap.resize(AZ_BANKS_NUM); // здесь окно 4 кб, значит банков - 16

	if (m_pMemoryMap.data())
	{
		m_nMemorySize = 32 * 1024 * 1024; // количество памяти 32Мб
		InitMemory(AZBK_PORTSIO_AREA);
		MemoryManager();
		g_Platform.GetWndVectorPtr()->push_back({ _T("AZBK"), -1, 010000, GetMainMemory() });
		return true;
	}

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

bool CAZBK::InitMemory(const uint16_t wHighLim)
{
	m_nBKPortsIOArea = wHighLim;
	m_pMemory.clear();

	if (m_nMemorySize)
	{
		m_pMemory.resize(m_nMemorySize);

		if (m_pMemory.data())
		{
			m_pAZBlt->AttachMemory(reinterpret_cast<uint16_t *>(m_pMemory.data()));
			ZeroMemory(m_pMemory.data(), m_nMemorySize);

			for (int i = 0; i < AZ_BANKS_NUM; ++i)
			{
				BKMEMBank_t &m = m_pMemoryMap[i];
				m.bReadable = FALSE;
				m.bWritable = FALSE;
				m.nBank = i;
				m.nOffset = AZ_BANK_TO_OFFSET(i);
				m.nTimingCorrection = RAM_TIMING_CORR_VALUE_AZ;
			}

			m_AZRegs.nAZ_win_ctrl = 0;
			m_AZRegs.nAZ_win_ro = 0;
			m_AZRegs.nAZ_win_shdw = 0;
			m_AZRegs.nAZ_mapper_ctrl = AZ_version;
			return true;
		}

		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return false;
}

void CAZBK::MemoryManager()
{
	for (int i = 0, mask = 1; i < AZ_BANKS_NUM; ++i, mask <<= 1)
	{
		BKMEMBank_t &m = m_pMemoryMap[i];
		if (m_AZRegs.nAZ_win_ctrl & mask)
		{
			m.bReadable = TRUE;

			if (0100 <= m.nBank && m.nBank < 0200) // RO память, отведённая под ПЗУ
			{
				m.bWritable = FALSE; // безусловно аппаратное ограничение
			}
			else
			{
				if (m_AZRegs.nAZ_win_ro & mask)
				{
					// режим доступа RO
					m.bWritable = FALSE;
				}
				else
				{
					// режим доступа RW
					m.bWritable = TRUE;
				}
			}
		}
		else
		{
			// если в окно ничего не подключено
			m.bReadable = FALSE;
			m.bWritable = FALSE;
		}
	}
}

// подключение страницы в окно.
// вход: nWnd - номер окна в адресном пространстве
//      nPage - номер окна 4кб - сегмента физической памяти
//      bRW - режим доступа
//      bForce - если страница аппратно выключена, принудительно включить
void CAZBK::MapToWindow(const int nWnd, const int nPage, const bool bRW)
{
	const uint16_t mask = 1 << nWnd;
	m_AZRegs.nAZ_win_ctrl |= mask;
	BKMEMBank_t &m = m_pMemoryMap[nWnd];
	m.bReadable = TRUE;
	m.nBank = nPage & 0017777;
	m.nOffset = AZ_BANK_TO_OFFSET(m.nBank);

	if (0100 <= m.nBank && m.nBank < 0200) // RO память, отведённая под ПЗУ
	{
		m.bWritable = FALSE; // безусловно аппаратное ограничение
	}
	else
	{
		if (bRW)
		{
			m_AZRegs.nAZ_win_ro &= ~mask;
			m.bWritable = TRUE;
		}
		else
		{
			m_AZRegs.nAZ_win_ro |= mask;
			m.bWritable = FALSE;
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Эта функция используется единственный раз при начальной инициализации модели.
bool CAZBK::InitDevice(const uint16_t addrStart)
{
	m_pTexture = g_Platform.GetScreenPtr()->getAZBKTexture();

	if (m_pAZCtrl->Init(GetMainMemory()))
	{
		// перманентно выключаем ПЗУ.
		const auto brd = g_Platform.GetBoardPtr();
		brd->BasicRomOFF(true);
		brd->Mon10RomOFF(true);
		brd->BOS11RomOFF(true);
		brd->RAM11Wn1OFF(false); // включаем страницу ОЗУ БК
		ResetCold();
		return true;
	}
	m_AZRegs.nBLTC177270 = 0;

	return false;
}

/////////////////////////////////////////////////////////////////////////////////
// это ресет оборудования, выполняемый по команде RESET. вызывается из CPU
void CAZBK::OnReset()
{
	// AZBK игнорирует сигнал INIT
	m_pAZCtrl->Reset();

	if (g_Platform.GetAYSndPtr())
	{
		g_Platform.GetAYSndPtr()->Reset();
	}
}

void CAZBK::ResetCold()
{
	m_AZRegs.nAZ_win_ctrl = 0;
	m_AZRegs.nAZ_win_ro = 0;
	m_AZRegs.nAZ_win_shdw = 0;
	m_AZRegs.nAZ_mapper_ctrl &= ~(AZ_MMU_SMK_WND1_ENA  // сбрасываем блокировку окна 1 в БК11М
	                              | AZ_MMU_V100_ON      // выключаем кадровый таймер явно
	                              | AZ_MMU_014_OFF      // сбросим режимы доработки в первоначальное состояние
	                              | AZ_MMU_037_OFF);    // сбросим режимы доработки в первоначальное состояние
	m_AZData.pre_b11m_reg_az_win_ctrl = 0;
	m_AZData.pre_b11m_reg_az_win_shdw = 0;
	m_AZData.pre_smk_reg_az_win_ctrl = 0;
	m_AZData.pre_smk_reg_az_win_ro = 0;

	for (int i = 0; i < 8; ++i)
	{
		m_AZData.pre_smk_reg_az_mapper[i] = 0;
		m_AZData.pre_b11m_reg_az_mapper[i] = 0;
	}

	if (!g_Config.m_bAZWin1Off) // доработка 037
	{
		m_AZRegs.nAZ_mapper_ctrl |= AZ_MMU_WND1_REVTYPE;
		m_AZRegs.nAZ_mapper_ctrl &= ~AZ_MMU_SMK_WND1_REV; // сбросим если есть доработка 037й
	}
	else
	{
		m_AZRegs.nAZ_mapper_ctrl &= ~AZ_MMU_WND1_REVTYPE;
	}

	// подключаем стартовое ПЗУ
	MapToWindow(15, 0100, false);
	m_AZData.bSEL1 = ((m_AZRegs.nAZ_mapper_ctrl & 0700) != 0700);
}


void CAZBK::NextTick()
{
	m_AZData.bSEL1 = true;
}

bool CAZBK::GetSystemRegister(const uint16_t addr, void *pDst, UINT dwFlags /*= 0*/)
{
	bool bRet = true;
	uint16_t v = 0;

	switch (addr & 0177776)
	{
		case REG_DMA_CSR:
			v = m_AZRegs.nDMACSR;
			break;

		case REG_DMA_MEM:
			v = m_AZRegs.nDMAStartPage;
			break;

		case REG_DMA_LEN_HI:
			v = (m_AZRegs.nDMAFileSize >> 16) & 0xff;
			break;

		case REG_DMA_LEN_LO:
			v = m_AZRegs.nDMAFileSize & 0x0000ffff;
			break;

		case REG_DMA_PGNUM:
			v = m_AZRegs.nDMACurrentPage;
			break;

		case REG_AY_ADR1:
			if (dwFlags & GSSR_BYTEOP)
			{
				if (addr & 1)
				{
					v = m_AZRegs.nAY1Data;
				}
				else
				{
					v = m_AZRegs.nAY1Addr;
				}
			}
			else
			{
				v = (static_cast<uint16_t>(m_AZRegs.nAY1Data) << 8) | m_AZRegs.nAY1Addr;
			}

			break;

		case REG_AY_ADR2:
			if (dwFlags & GSSR_BYTEOP)
			{
				if (addr & 1)
				{
					v = m_AZRegs.nAY2Data;
				}
				else
				{
					v = m_AZRegs.nAY2Addr;
				}
			}
			else
			{
				v = (static_cast<uint16_t>(m_AZRegs.nAY2Data) << 8) | m_AZRegs.nAY2Addr;
			}

			break;

		case REG_OPL2_ADDR:
			if (dwFlags & GSSR_BYTEOP)
			{
				if (addr & 1)
				{
					v = 0;
				}
			}
			v = 0;
			break;

		case REG_CVX_L16:
			v = m_AZRegs.nCVXLeft;
			break;

		case REG_CVX_R16:
			v = m_AZRegs.nCVXRight;
			break;

		case REG_CVX_M16:
			v = m_AZRegs.nCVXMono; // будем возвращать левое значение
			break;

		case REG_CVX_LR8:
			if (dwFlags & GSSR_BYTEOP)
			{
				v = m_AZRegs.nCVXLeft >> 8;
			}
			else
			{
				v = (m_AZRegs.nCVXLeft >> 8) | (m_AZRegs.nCVXRight & 0177400);
			}

			break;

		case REG_CVX_CSR:
			v = m_AZRegs.nCVXCSR;
			break;

		case REG_CSR:
			v = m_pAZCtrl->ReadCSR();
			break;

		case REG_DR:
			if (dwFlags & GSSR_INTERNAL)
			{
				v = m_pAZCtrl->ReadDR_int();
			}
			else
			{
				if (!m_pAZCtrl->ReadDR(&v))
				{
					return false;
				}
			}

			break;

		case REG_BR1:  // на БК не используется
			if (!(dwFlags & GSSR_INTERNAL))
			{
				if (!m_pAZCtrl->ReadBR1(&v))
				{
					return false;
				}
			}

			v = 0;
			break;

		case REG_BR2:  // на БК не используется
			if (!(dwFlags & GSSR_INTERNAL))
			{
				if (!m_pAZCtrl->ReadBR2(&v))
				{
					return false;
				}
			}

			v = 0776;
			break;

		case REG_SCR_CSR:
			v = m_AZRegs.nSCRCSR;
			break;

		case REG_SCR_PGNUM_L0:
			v = m_AZRegs.nSCRPageNum_L0;
			break;

		case REG_SCR_PGNUM_L1:
			v = m_AZRegs.nSCRPageNum_L1;
			break;

		case REG_SCR_PGNUM_L2:
			v = m_AZRegs.nSCRPageNum_L2;
			break;

		case REG_PAL_CELL:
			v = m_AZRegs.nPalCell;
			break;

		case REG_PAL_VAL:
			v = (m_AZRegs.nPalCell < AZ_PALETTE_SIZE) ? RGBtoR5G5B5(m_azPalette[m_AZRegs.nPalCell]) : 0;
			break;

		case REG_SCR_VSCRL_L2:
			v = m_AZRegs.nVSCRL177244;
			break;
		case REG_SCR_VSCRL_L1:
			v = m_AZRegs.nVSCRL177246;
			break;
		case REG_SCR_VSCRL_L0:
			v = m_AZRegs.nVSCRL177250;
			break;

		case REG_SCR_HSCRL_L0:
			v = m_AZRegs.nHSCRL177252;
			break;

		case REG_SCR_HSCRL_L1:
			v = m_AZRegs.nHSCRL177254;
			break;

		case REG_SCR_HSCRL_L2:
			v = m_AZRegs.nHSCRL177256;
			break;

		case REG_BLT_CMD:
			v = m_AZRegs.nBLTC177270;
			break;
		case REG_BLT_PGN:
			v = m_AZRegs.nBLTP177272;
			break;

		case REG_MMU_PG00:
		case REG_MMU_PG01:
		case REG_MMU_PG02:
		case REG_MMU_PG03:
		case REG_MMU_PG04:
		case REG_MMU_PG05:
		case REG_MMU_PG06:
		case REG_MMU_PG07:
		case REG_MMU_PG08:
		case REG_MMU_PG09:
		case REG_MMU_PG10:
		case REG_MMU_PG11:
		case REG_MMU_PG12:
		case REG_MMU_PG13:
		case REG_MMU_PG14:
		case REG_MMU_PG15:
		{
			const uint32_t n = (addr - REG_MMU_PG00) >> 1; //получим номер окна
			v = Get_AZ_Mapper(n);
		}
		break;

		case REG_MMU_MNT:
			v = m_AZRegs.nAZ_win_ctrl;
			break;

		case REG_MMU_ACS:
			v = m_AZRegs.nAZ_win_ro;
			break;

		case REG_MMU_SHADOW:
			v = m_AZRegs.nAZ_win_shdw;
			break;

		case REG_MMU_CSR:
			v = m_AZRegs.nAZ_mapper_ctrl;
			break;

		case REG_MMU_AZ_130:
			v = m_AZRegs.nMMUAZ130;
			break;

		case REG_MMU_AZ_716:
			v = m_AZRegs.nMMUAZ716;
			break;

		case REG_VERS:
			v = m_pAZCtrl->GetFPGAVersion();
			break;

		case REG_RND:
			m_nRND = (m_nRND >> 5) | (m_nRND<<27);
			m_nRND ^= rand();
			v = m_nRND & 0xffff;
			break;

		case REG_TKS:
			v = m_AZRegs.nCOM177560;
			break;

		case REG_TKB:
			v = m_AZRegs.nCOM177562;
			break;

		case REG_TPS:
			v = m_AZRegs.nCOM177564;
			break;

		case REG_TPB:
			v = m_AZRegs.nCOM177566;
			break;

		case 0177716:
			v = AZ_716_Out();
			break;

		// заглушки
		case 0177130:
		case 0177132:
			v = 0;
			break;

		default:
			return false;
	}

	if (dwFlags & GSSR_BYTEOP)
	{
		*(reinterpret_cast<uint8_t *>(pDst)) = (addr & 1) ? HIBYTE(v) : LOBYTE(v);
	}
	else
	{
		*(reinterpret_cast<uint16_t *>(pDst)) = v;
	}

	return true;
}

bool CAZBK::SetSystemRegister(const uint16_t addr, uint16_t value, UINT dwFlags /*= 0*/)
{
	if (dwFlags & GSSR_BYTEOP)
	{
		value &= 0377; // запись в младший байт - как слово с обнулённым старшим байтом

		if (addr & 1)
		{
			switch (addr)
			{
				case REG_AY_DAT1:
					m_AZRegs.nAY1Data = static_cast<uint8_t>(value);

					if (g_Platform.GetAYSndPtr())
					{
						g_Platform.GetAYSndPtr()->write_data_AZ(AY1, static_cast<uint8_t>(value));
					}

					break;

				case REG_AY_DAT2:
					m_AZRegs.nAY2Data = static_cast<uint8_t>(value);

					if (g_Platform.GetAYSndPtr())
					{
						g_Platform.GetAYSndPtr()->write_data_AZ(AY2, static_cast<uint8_t>(value));
					}

					break;

				case REG_OPL2_DATA:
					break;
			}

			return true; // запись в старший байт для всего остального игнорируется.
		}

		switch (addr)
		{
			case REG_AY_ADR1:
				m_AZRegs.nAY1Addr = static_cast<uint8_t>(value);

				if (g_Platform.GetAYSndPtr())
				{
					g_Platform.GetAYSndPtr()->write_address_AZ(AY1, static_cast<uint8_t>(value));
				}

				return true;

			case REG_AY_ADR2:
				m_AZRegs.nAY2Addr = static_cast<uint8_t>(value);

				if (g_Platform.GetAYSndPtr())
				{
					g_Platform.GetAYSndPtr()->write_address_AZ(AY2, static_cast<uint8_t>(value));
				}

				return true;

			case REG_OPL2_ADDR:
				return true;
		}
	}

	switch (addr & 0177776)
	{
		case REG_DMA_CSR:
			m_AZRegs.nDMACSR = value;
			SetDMAWork();
			return true;

		case REG_DMA_MEM:
			m_AZRegs.nDMAStartPage = value;
			return true;

		case REG_DMA_LEN_HI:
			m_AZRegs.nDMAFileSize = (m_AZRegs.nDMAFileSize & 0x0000ffff) | ((value & 0xff) << 16);
			return true;

		case REG_DMA_LEN_LO:
			m_AZRegs.nDMAFileSize = (m_AZRegs.nDMAFileSize & 0x00ff0000) | value;
			return true;

		case REG_DMA_PGNUM:
			// m_AZRegs.nDMACurrentPage = value; запись игнорируем
			return true;

		case REG_AY_ADR1: // словная запись в AY1
			m_AZRegs.nAY1Addr = value & 0377;
			m_AZRegs.nAY1Data = (value >> 8) & 0377;

			if (g_Platform.GetAYSndPtr())
			{
				g_Platform.GetAYSndPtr()->write_address_AZ(AY1, m_AZRegs.nAY1Addr);
				g_Platform.GetAYSndPtr()->write_data_AZ(AY1, m_AZRegs.nAY1Data);
			}

			return true;

		case REG_AY_ADR2:
			m_AZRegs.nAY2Addr = value & 0377;
			m_AZRegs.nAY2Data = (value >> 8) & 0377;

			if (g_Platform.GetAYSndPtr())
			{
				g_Platform.GetAYSndPtr()->write_address_AZ(AY2, m_AZRegs.nAY2Addr);
				g_Platform.GetAYSndPtr()->write_data_AZ(AY2, m_AZRegs.nAY2Data);
			}

			return true;

		case REG_OPL2_ADDR:
			return true;

		case REG_CVX_L16:
			m_AZRegs.nCVXLeft = value;

			if (g_Platform.GetCovoxPtr())
			{
				g_Platform.GetCovoxPtr()->SetAZDataL(value);
			}

			return true;

		case REG_CVX_R16:
			m_AZRegs.nCVXRight = value;

			if (g_Platform.GetCovoxPtr())
			{
				g_Platform.GetCovoxPtr()->SetAZDataR(value);
			}

			return true;

		case REG_CVX_M16:
			m_AZRegs.nCVXLeft = m_AZRegs.nCVXRight = m_AZRegs.nCVXMono = value;

			if (g_Platform.GetCovoxPtr())
			{
				g_Platform.GetCovoxPtr()->SetAZDataLR(value, value);
			}

			return true;

		case REG_CVX_LR8:
			if (dwFlags & GSSR_BYTEOP)
			{
				m_AZRegs.nCVXMono = m_AZRegs.nCVXLeft = m_AZRegs.nCVXRight = (value & 0xff) << 7;
			}
			else
			{
				m_AZRegs.nCVXLeft = (value & 0xff) << 7;
				m_AZRegs.nCVXRight = (value & 0xff00) >> 1;
			}

			if (g_Platform.GetCovoxPtr())
			{
				g_Platform.GetCovoxPtr()->SetAZDataLR(m_AZRegs.nCVXLeft, m_AZRegs.nCVXRight);
			}

			return true;

		case REG_CVX_CSR:
			m_AZRegs.nCVXCSR = value & 0x7;
			return true;

		case REG_CSR:
			return m_pAZCtrl->WriteCSR(value);

		case REG_DR:
			return m_pAZCtrl->WriteDR(value);

		case REG_BR1: // на БК не используется
			return m_pAZCtrl->WriteBR1(value);

		case REG_BR2: // на БК не используется
			return m_pAZCtrl->WriteBR2(value);

		case REG_SCR_CSR:
			m_AZRegs.nSCRCSR = value;
			SetScreenParam(value);
			return true;

		case REG_SCR_PGNUM_L0:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_AZRegs.nSCRPageNum_L0 = value & 0017777;
		}

		return true;

		case REG_SCR_PGNUM_L1:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_AZRegs.nSCRPageNum_L1 = value & 0017777;
		}

		return true;

		case REG_SCR_PGNUM_L2:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_AZRegs.nSCRPageNum_L2 = value & 0017777;
		}

		return true;

		case REG_PAL_CELL:
			m_AZRegs.nPalCell = value & 0x1ff;
			return true;

		case REG_PAL_VAL:
			if (m_AZRegs.nPalCell < AZ_PALETTE_SIZE)
			{
				m_azPalette[m_AZRegs.nPalCell] = R5G5B5toRGB(value);
			}

			return true;

		case REG_SCR_VSCRL_L2:
			m_AZRegs.nVSCRL177244 = value & 0xff;
			return true;
		case REG_SCR_VSCRL_L1:
			m_AZRegs.nVSCRL177246 = value & 0xff;
			return true;
		case REG_SCR_VSCRL_L0:
			m_AZRegs.nVSCRL177250 = value & 0xff;
			return true;

		case REG_SCR_HSCRL_L0:
			m_AZRegs.nHSCRL177252 = value & 0xff;
			return true;
		case REG_SCR_HSCRL_L1:
			m_AZRegs.nHSCRL177254 = value & 0xff;
			return true;
		case REG_SCR_HSCRL_L2:
			m_AZRegs.nHSCRL177256 = value & 0xff;
			return true;

		case REG_BLT_CMD:
			m_AZRegs.nBLTC177270 = value & 0xff;
			if (m_AZRegs.nBLTC177270 == 0)
			{
				m_pAZBlt->Reset();
			}
			return true;

		case REG_BLT_PGN:
			m_AZRegs.nBLTP177272 = value & 017777;
			return true;

		case REG_MMU_PG00:
		case REG_MMU_PG01:
		case REG_MMU_PG02:
		case REG_MMU_PG03:
		case REG_MMU_PG04:
		case REG_MMU_PG05:
		case REG_MMU_PG06:
		case REG_MMU_PG07:
		case REG_MMU_PG08:
		case REG_MMU_PG09:
		case REG_MMU_PG10:
		case REG_MMU_PG11:
		case REG_MMU_PG12:
		case REG_MMU_PG13:
		case REG_MMU_PG14:
		case REG_MMU_PG15:
		{
			const uint32_t n = (addr - REG_MMU_PG00) >> 1; //получим номер окна
			Set_AZ_Mapper(n, value);
		}

		MemoryManager();
		return true;

		case REG_MMU_MNT:
			m_AZRegs.nAZ_win_ctrl = value;
			MemoryManager();
			return true;

		case REG_MMU_ACS:
			m_AZRegs.nAZ_win_ro = value;
			MemoryManager();
			return true;

		case REG_MMU_SHADOW:
			m_AZRegs.nAZ_win_shdw = value;
			//MemoryManager();
			return true;

		case REG_MMU_CSR:
			m_AZRegs.nAZ_mapper_ctrl &= (AZ_MMU_WND1_REVTYPE | 3);
			m_AZRegs.nAZ_mapper_ctrl |= (value & ~(AZ_MMU_WND1_REVTYPE | 3));
			{
				const auto brd = g_Platform.GetBoardPtr();
				// отключение окна 1 на БК11М
				brd->RAM11Wn1OFF(!!(m_AZRegs.nAZ_mapper_ctrl & AZ_MMU_SMK_WND1_ENA));
			}

			// управление отключением 037
			if (g_Config.m_bAZ037Off)
			{
				g_Config.m_bAZ037OffState = (m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_WND1_REVTYPE | AZ_MMU_037_OFF)) == (AZ_MMU_WND1_REVTYPE | AZ_MMU_037_OFF);
				const auto brd = g_Platform.GetBoardPtr();
				brd->RAM037FF();
			}

			// управление отключением 014
			if (g_Config.m_bAZ014Off)
			{
				g_Config.m_bAZ014OffState = (m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_WND1_REVTYPE | AZ_MMU_014_OFF)) == (AZ_MMU_WND1_REVTYPE | AZ_MMU_014_OFF);
			}

			// управление таймером 50 Гц
			if ((m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER)) == (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER))
			{
				Start50НzThread();
			}
			else
			{
				Stop50НzThread();
			}

			return true;

		case REG_VERS:
		case REG_RND: //запись игнорируется.
			return true;

		// заглушки
		case 0177130:
			// тут надо добавить манипуляцию дозу
			AZ_130_In(value);
			[[fallthrough]];
		case 0177132:
			return true;

		case REG_TKS:
			m_AZRegs.nCOM177560 = value;
			return true;

		case REG_TKB:
			m_AZRegs.nCOM177562 = value;
			return true;

		case REG_TPS:
			m_AZRegs.nCOM177564 = value;
			return true;

		case REG_TPB:
			m_AZRegs.nCOM177566 = value;
			return true;

		case 0177662:
		{
			const uint16_t t = (m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_BK11EMU_ENA | AZ_MMU_BRDTYPE));
			const bool b11Memu = (t == (AZ_MMU_BK11EMU_ENA | AZ_MMU_BRDTYPE));

			if (b11Memu ||  // включаем запись в 177662 при эмуляции БК11М на БК10
			        !t)    // включаем запись в 177662 на 11М при отключенной эмуляции 10ки
			{
				std::lock_guard<std::mutex> lk(m_mutScrParam);
				m_AZData.nLegacyPalette = (value >> 8) & 0xf;
				m_AZData.nLegacyScrBuf = (value & 0100000) ? 1 : 0;
				m_AZRegs.nSCRPageNum_L0 = m_AZData.nLegacyScrBuf ? 040000 * 7 / 010000 : 040000 / 010000;
			}

			if (b11Memu)
			{
				if (value & 040000)
				{
					m_AZRegs.nAZ_mapper_ctrl &= ~(AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER); // чтобы нельзя было включить оба таймера одновременно
					Stop50НzThread();
				}
				else
				{
					m_AZRegs.nAZ_mapper_ctrl |= (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER); // чтобы нельзя было включить оба таймера одновременно
					Start50НzThread();
				}
			}

			return b11Memu; // RPLY выдавать - если эмуляция БК11 на БК10, иначе - не выдавать.
		}

		case 0177664:
			m_AZRegs.nVSCRL177250 = (value & 0377) - 0330; // скроллинг 256 -> 768 (единица - сдвиг на три строки)
			m_AZData.bAZExtended = !(value & 01000);
			return false; // RPLY не выдавать

		case 0177714:
			if (!(m_AZRegs.nCVXCSR & 2))
			{
				if (m_AZRegs.nCVXCSR & 1)
				{
					m_AZRegs.nCVXLeft = value << 8;
					m_AZRegs.nCVXRight = value & 0xff00;
				}
				else
				{
					m_AZRegs.nCVXMono = m_AZRegs.nCVXLeft = m_AZRegs.nCVXRight = value << 8;
				}

				if (g_Platform.GetCovoxPtr())
				{
					g_Platform.GetCovoxPtr()->SetAZDataLR(m_AZRegs.nCVXLeft, m_AZRegs.nCVXRight);
				}
			}

			return false; // RPLY не выдавать

		case 0177716:
			if (!AZ_716_In(value))
			{
				if (!(m_AZRegs.nCVXCSR & 4))
				{
					uint16_t w = value & 0144; // получим маску звуковых битов

					if (w & 04) // 2й бит переместим в 4й
					{
						w |= 020;
					}

					m_AZRegs.nCVXMono = m_AZRegs.nCVXLeft = m_AZRegs.nCVXRight = w << 9;

					if (g_Platform.GetCovoxPtr())
					{
						g_Platform.GetCovoxPtr()->SetAZDataLR(m_AZRegs.nCVXLeft, m_AZRegs.nCVXRight);
					}
				}
			}

			return false; // RPLY не выдавать
	}

	return false;
}

bool CAZBK::GetByteT(const uint16_t addr, uint8_t *pValue, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];

	// Сперва проверим, на системные регистры
	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// в обычном режиме тут только регистры могут быть
		return GetSystemRegister(addr, pValue, GSSR_BYTEOP);
	}

	// теперь проверим на чтение из несуществующего банка
	if (memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007777)];
		return true;
	}

	nTC = 0;
	*pValue = 0;
	return false;
}

bool CAZBK::GetWordT(const uint16_t addr, uint16_t *pValue, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];

	// Сперва проверим, на системные регистры
	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// в обычном режиме тут только регистры могут быть
		return GetSystemRegister(addr, pValue, GSSR_NONE);
	}

	// теперь проверим на чтение из несуществующего банка
	if (memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007776)]));
		return true;
	}

	nTC = 0;
	*pValue = 0;
	return false;
}

void CAZBK::DropLegacyShadowMap(const uint16_t addr, uint16_t value, UINT dwFlags/* = 0*/)
{
	const int nBnk = AZ_ADDRESS_TO_BANK(addr);
	const uint16_t nBit = 1 << nBnk;

	if ((nBnk < 0100 || nBnk >= 0200)     // если не диапазон эмуляции ПЗУ
	        && !(m_AZRegs.nAZ_win_ctrl & nBit) && (m_AZRegs.nAZ_win_shdw & nBit)) // и страница выключена и шадов включен
	{
		// делаем
		// но нужно более уточнений
		const size_t nOffset = m_pMemoryMap[nBnk].nOffset;

		if (dwFlags & GSSR_BYTEOP)
		{
			m_pMemory[nOffset + (addr & 07777)] = value & 0377;
		}
		else
		{
			*(reinterpret_cast<uint16_t *>(&m_pMemory[nOffset + (addr & 07776)])) = value;
		}
	}
}

bool CAZBK::SetByteT(const uint16_t addr, uint8_t value, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];
	DropLegacyShadowMap(addr, value, GSSR_BYTEOP);

	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// тут только регистры могут быть
		return SetSystemRegister(addr, value, GSSR_BYTEOP);
	}

	if (memoryBank.bWritable)
	{
		nTC = memoryBank.nTimingCorrection;
		m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007777)] = value;
		return true;
	}

	nTC = 0;
	return false;
}

bool CAZBK::SetWordT(const uint16_t addr, uint16_t value, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];
	DropLegacyShadowMap(addr, value, GSSR_NONE);

	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// тут только регистры могут быть
		return SetSystemRegister(addr, value, GSSR_NONE);
	}

	if (memoryBank.bWritable)
	{
		nTC = memoryBank.nTimingCorrection;
		*(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007776)])) = value;
		return true;
	}

	nTC = 0;
	return false;
}

bool CAZBK::GetByteTInternal(const uint16_t addr, uint8_t *pValue, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];

	// Сперва проверим, на системные регистры
	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// в обычном режиме тут только регистры могут быть
		return GetSystemRegister(addr, pValue, GSSR_BYTEOP | GSSR_INTERNAL);
	}

	// теперь проверим на чтение из несуществующего банка
	if (memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007777)];
		return true;
	}

	nTC = 0;
	*pValue = 0;
	return false;
}

bool CAZBK::GetWordTInternal(const uint16_t addr, uint16_t *pValue, int &nTC)
{
	const int nBank = AZ_ADDRESS_TO_BANK(addr);
	const BKMEMBank_t &memoryBank = m_pMemoryMap[nBank];

	// Сперва проверим, на системные регистры
	if (nBank == 15 && addr >= m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		// в обычном режиме тут только регистры могут быть
		return GetSystemRegister(addr, pValue, GSSR_NONE | GSSR_INTERNAL);
	}

	// теперь проверим на чтение из несуществующего банка
	if (memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 007776)]));
		return true;
	}

	nTC = 0;
	*pValue = 0;
	return false;
}

bool CAZBK::RestoreRegisters(DWORD id, CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		return pMsf->GetBlockCustomData(MSF_BLOCKTYPE::PORT_REGSEX, id, reinterpret_cast<uint8_t *>(&m_AZRegs), sizeof(sAZRegisters_t));
	}

	return pMsf->SetBlockCustomData(MSF_BLOCKTYPE::PORT_REGSEX, id, reinterpret_cast<uint8_t *>(&m_AZRegs), sizeof(sAZRegisters_t));
}

bool CAZBK::RestoreMemory(DWORD id, CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		return pMsf->GetBlockBaseMemory(id, GetMainMemory(), m_nMemorySize);
	}

	return pMsf->SetBlockBaseMemoryEx(id, GetMainMemory(), m_nMemorySize, true);
}

bool CAZBK::RestoreMemoryMap(DWORD id, CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		return pMsf->GetBlockMemMap(id, m_pMemoryMap.data(), AZ_BANKS_NUM, m_nROMPresent);
	}

	return pMsf->SetBlockMemMap(id, m_pMemoryMap.data(), AZ_BANKS_NUM, m_nROMPresent);
}

bool CAZBK::RestoreCustomData(DWORD id, CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		return pMsf->GetBlockCustomData(MSF_BLOCKTYPE::CUSTOMEX, id, reinterpret_cast<uint8_t *>(&m_AZData), sizeof(sAZCustom_t));
	}

	return pMsf->SetBlockCustomData(MSF_BLOCKTYPE::CUSTOMEX, id, reinterpret_cast<uint8_t *>(&m_AZData), sizeof(sAZCustom_t));
}

