#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] =
{
	0x0, 0x10, 0x1F, 0x200, 0x210, 0x3E0, 0x3FF, 0x4000, 0x4010, 0x4200, 0x4210, 0x6318, 0x7C00, 0x7C1F, 0x7FE0, 0x7FFF,
	0x0, 0x10, 0x1F, 0x200, 0x210, 0x3E0, 0x3FF, 0x4000, 0x4010, 0x4200, 0x4210, 0x6318, 0x7C00, 0x7C1F, 0x7FE0, 0x7FFF,
	0x421, 0x14A5, 0x1CE7, 0x2529, 0x2D6B, 0x35AD, 0x3DEF, 0x7FFF, 0x0, 0x6, 0xC, 0x13, 0x19, 0x1F, 0xC0, 0xC6, 0xCC, 0xD3,
	0xD9, 0xDF, 0x180, 0x186, 0x18C, 0x193, 0x199, 0x19F, 0x260, 0x266, 0x26C, 0x273, 0x279, 0x27F, 0x320, 0x326, 0x32C,
	0x333, 0x339, 0x33F, 0x3E0, 0x3E6, 0x3EC, 0x3F3, 0x3F9, 0x3FF, 0x1800, 0x1806, 0x180C, 0x1813, 0x1819, 0x181F, 0x18C0,
	0x18C6, 0x18CC, 0x18D3, 0x18D9, 0x18DF, 0x1980, 0x1986, 0x198C, 0x1993, 0x1999, 0x199F, 0x1A60, 0x1A66, 0x1A6C,
	0x1A73, 0x1A79, 0x1A7F, 0x1B20, 0x1B26, 0x1B2C, 0x1B33, 0x1B39, 0x1B3F, 0x1BE0, 0x1BE6, 0x1BEC, 0x1BF3, 0x1BF9,
	0x1BFF, 0x3000, 0x3006, 0x300C, 0x3013, 0x3019, 0x301F, 0x30C0, 0x30C6, 0x30CC, 0x30D3, 0x30D9, 0x30DF, 0x3180,
	0x3186, 0x318C, 0x3193, 0x3199, 0x319F, 0x3260, 0x3266, 0x326C, 0x3273, 0x3279, 0x327F, 0x3320, 0x3326, 0x332C,
	0x3333, 0x3339, 0x333F, 0x33E0, 0x33E6, 0x33EC, 0x33F3, 0x33F9, 0x33FF, 0x4C00, 0x4C06, 0x4C0C, 0x4C13, 0x4C19,
	0x4C1F, 0x4CC0, 0x4CC6, 0x4CCC, 0x4CD3, 0x4CD9, 0x4CDF, 0x4D80, 0x4D86, 0x4D8C, 0x4D93, 0x4D99, 0x4D9F, 0x4E60,
	0x4E66, 0x4E6C, 0x4E73, 0x4E79, 0x4E7F, 0x4F20, 0x4F26, 0x4F2C, 0x4F33, 0x4F39, 0x4F3F, 0x4FE0, 0x4FE6, 0x4FEC,
	0x4FF3, 0x4FF9, 0x4FFF, 0x6400, 0x6406, 0x640C, 0x6413, 0x6419, 0x641F, 0x64C0, 0x64C6, 0x64CC, 0x64D3, 0x64D9,
	0x64DF, 0x6580, 0x6586, 0x658C, 0x6593, 0x6599, 0x659F, 0x6660, 0x6666, 0x666C, 0x6673, 0x6679, 0x667F, 0x6720,
	0x6726, 0x672C, 0x6733, 0x6739, 0x673F, 0x67E0, 0x67E6, 0x67EC, 0x67F3, 0x67F9, 0x67FF, 0x7C00, 0x7C06, 0x7C0C,
	0x7C13, 0x7C19, 0x7C1F, 0x7CC0, 0x7CC6, 0x7CCC, 0x7CD3, 0x7CD9, 0x7CDF, 0x7D80, 0x7D86, 0x7D8C, 0x7D93, 0x7D99,
	0x7D9F, 0x7E60, 0x7E66, 0x7E6C, 0x7E73, 0x7E79, 0x7E7F, 0x7F20, 0x7F26, 0x7F2C, 0x7F33, 0x7F39, 0x7F3F, 0x7FE0,
	0x7FE6, 0x7FEC, 0x7FF3, 0x7FF9, 0x7FFF, 0x0, 0x1F, 0x3E0, 0x7C00, 0x0, 0x7FE0, 0x7C1F, 0x7C00, 0x0, 0x3FF, 0x1F,
	0x7C1F, 0x0, 0x3E0, 0x3FF, 0x7FE0, 0x0, 0x7C1F, 0x3FF, 0x7FFF, 0x0, 0x7FFF, 0x7FFF, 0x7FFF, 0x0, 0x5C00, 0x4C00,
	0x7C00, 0x0, 0x5FE0, 0x4FE0, 0x7FE0, 0x0, 0x5C1F, 0x4C1F, 0x7C1F, 0x0, 0x4FE0, 0x4C1F, 0x4C00, 0x0, 0x5FE0, 0x5C1F,
	0x5C00, 0x0, 0x3FF, 0x7FE0, 0x7C00, 0x0, 0x7C00, 0x3E0, 0x3FF, 0x0, 0x3FF, 0x7FE0, 0x7FFF, 0x0, 0x7FE0, 0x3E0, 0x7FFF,
	0x0, 0x3FF, 0x3E0, 0x7FFF, 0x0, 0x15, 0x2A0, 0x2B5, 0x5400, 0x5415, 0x5540, 0x56B5, 0x294A, 0x1F, 0x3E0, 0x3FF, 0x7C00,
	0x7C1F, 0x7FE0, 0x7FFF, 0x10, 0x7FFF
};

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);
	}
}

CAZBK::~CAZBK()
{
	StopDMAThread();
}

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(uint16_t wHighLim)
{
	m_nBKPortsIOArea = wHighLim;
	m_pMemory.clear();

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

		if (m_pMemory.data())
		{
			ZeroMemory(m_pMemory.data(), m_nMemorySize);

			for (int i = 0; i < AZ_BANKS_NUM; ++i)
			{
				m_pMemoryMap[i].bReadable = FALSE;
				m_pMemoryMap[i].bWritable = FALSE;
				m_pMemoryMap[i].nBank = i;
				m_pMemoryMap[i].nOffset = AZ_BANK_TO_OFFSET(i);
				m_pMemoryMap[i].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, m = 1; i < AZ_BANKS_NUM; ++i, m <<= 1)
	{
		if (m_AZRegs.nAZ_win_ctrl & m)
		{
			m_pMemoryMap[i].bReadable = TRUE;

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

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

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

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

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

	return false;
}

/////////////////////////////////////////////////////////////////////////////////
// это ресет оборудования, выполняемый по команде RESET. вызывается из CPU
void CAZBK::OnReset()
{
	// AZBK игнорирует сигнал INIT
	m_AZCrtl.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_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_AZCrtl.ReadCSR();
			break;

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

			break;

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

			v = 0;
			break;

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

			v = 0776;
			break;

		case REG_SCR_CSR:
			v = m_AZRegs.nSCRCSR;
			break;

		case REG_SCR_PGNUM:
			v = m_AZRegs.nSCRPageNum;
			break;

		case REG_SCR_SPR_PGNUM:
			v = m_AZRegs.nSCRSprPageNum;
			break;

		case REG_SCR_BGF_PGNUM:
			v = m_AZRegs.nSCRBgfPageNum;
			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:
			v = m_AZRegs.nSCRL177250;
			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_AZCrtl.GetFPGAVersion();
			break;

		case REG_RND:
			v = (rand() >> 5) & 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;
			}

			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;
		}
	}

	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_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_AZCrtl.WriteCSR(value);

		case REG_DR:
			return m_AZCrtl.WriteDR(value);

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

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

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

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

		return true;

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

		return true;

		case REG_SCR_BGF_PGNUM:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_AZRegs.nSCRBgfPageNum = 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:
			m_AZRegs.nSCRL177250 = value;
			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);

		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 = 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.nSCRL177250 = ((value - 0330) & 0377); // скроллинг 256 -> 768 (единица - сдвиг на три строки)
			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(id, reinterpret_cast<uint8_t *>(&m_AZRegs), sizeof(sAZRegisters_t));
	}

	return pMsf->SetBlockCustomData(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->SetBlockBaseMemory(id, GetMainMemory(), m_nMemorySize);
}

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())
	{
		bool bRet = pMsf->GetBlockCustomData(id, reinterpret_cast<uint8_t *>(&m_AZData), sizeof(sAZCustom_t));

		if (bRet)
		{
			if (m_AZData.bAZAltProMM)
			{
				AltPro_MemoryManager();
			}
			else
			{
				AZ_MemoryManager();
			}
		}

		return bRet;
	}

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

