#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, 0xff0f0f0f, 0xff101010, 0xff1f1f1f, 0xff202020, 0xff2f2f2f, 0xff303030, 0xff3f3f3f, 0xff404040, 0xff4f4f4f, 0xff505050, 0xff5f5f5f, 0xff606060, 0xff6f6f6f, 0xff707070, 0xff7f7f7f,
	0xff808080, 0xff8f8f8f, 0xff909090, 0xff9f9f9f, 0xffa0a0a0, 0xffafafaf, 0xffb0b0b0, 0xffbfbfbf, 0xffc0c0c0, 0xffcfcfcf, 0xffd0d0d0, 0xffdfdfdf, 0xffe0e0e0, 0xffefefef, 0xfff0f0f0, 0xffffffff,
	0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff, 0xff000000, 0xff000033, 0xff000066, 0xff000099, 0xff0000cc, 0xff0000ff, 0xff003300, 0xff003333,
	0xff003366, 0xff003399, 0xff0033cc, 0xff0033ff, 0xff006600, 0xff006633, 0xff006666, 0xff006699, 0xff0066cc, 0xff0066ff, 0xff009900, 0xff009933, 0xff009966, 0xff009999, 0xff0099cc, 0xff0099ff,
	0xff00cc00, 0xff00cc33, 0xff00cc66, 0xff00cc99, 0xff00cccc, 0xff00ccff, 0xff00ff00, 0xff00ff33, 0xff00ff66, 0xff00ff99, 0xff00ffcc, 0xff00ffff, 0xff330000, 0xff330033, 0xff330066, 0xff330099,
	0xff3300cc, 0xff3300ff, 0xff333300, 0xff333333, 0xff333366, 0xff333399, 0xff3333cc, 0xff3333ff, 0xff336600, 0xff336633, 0xff336666, 0xff336699, 0xff3366cc, 0xff3366ff, 0xff339900, 0xff339933,
	0xff339966, 0xff339999, 0xff3399cc, 0xff3399ff, 0xff33cc00, 0xff33cc33, 0xff33cc66, 0xff33cc99, 0xff33cccc, 0xff33ccff, 0xff33ff00, 0xff33ff33, 0xff33ff66, 0xff33ff99, 0xff33ffcc, 0xff33ffff,
	0xff660000, 0xff660033, 0xff660066, 0xff660099, 0xff6600cc, 0xff6600ff, 0xff663300, 0xff663333, 0xff663366, 0xff663399, 0xff6633cc, 0xff6633ff, 0xff666600, 0xff666633, 0xff666666, 0xff666699,
	0xff6666cc, 0xff6666ff, 0xff669900, 0xff669933, 0xff669966, 0xff669999, 0xff6699cc, 0xff6699ff, 0xff66cc00, 0xff66cc33, 0xff66cc66, 0xff66cc99, 0xff66cccc, 0xff66ccff, 0xff66ff00, 0xff66ff33,
	0xff66ff66, 0xff66ff99, 0xff66ffcc, 0xff66ffff, 0xff990000, 0xff990033, 0xff990066, 0xff990099, 0xff9900cc, 0xff9900ff, 0xff993300, 0xff993333, 0xff993366, 0xff993399, 0xff9933cc, 0xff9933ff,
	0xff996600, 0xff996633, 0xff996666, 0xff996699, 0xff9966cc, 0xff9966ff, 0xff999900, 0xff999933, 0xff999966, 0xff999999, 0xff9999cc, 0xff9999ff, 0xff99cc00, 0xff99cc33, 0xff99cc66, 0xff99cc99,
	0xff99cccc, 0xff99ccff, 0xff99ff00, 0xff99ff33, 0xff99ff66, 0xff99ff99, 0xff99ffcc, 0xff99ffff, 0xffcc0000, 0xffcc0033, 0xffcc0066, 0xffcc0099, 0xffcc00cc, 0xffcc00ff, 0xffcc3300, 0xffcc3333,
	0xffcc3366, 0xffcc3399, 0xffcc33cc, 0xffcc33ff, 0xffcc6600, 0xffcc6633, 0xffcc6666, 0xffcc6699, 0xffcc66cc, 0xffcc66ff, 0xffcc9900, 0xffcc9933, 0xffcc9966, 0xffcc9999, 0xffcc99cc, 0xffcc99ff,
	0xffcccc00, 0xffcccc33, 0xffcccc66, 0xffcccc99, 0xffcccccc, 0xffccccff, 0xffccff00, 0xffccff33, 0xffccff66, 0xffccff99, 0xffccffcc, 0xffccffff, 0xffff0000, 0xffff0033, 0xffff0066, 0xffff0099,
	0xffff00cc, 0xffff00ff, 0xffff3300, 0xffff3333, 0xffff3366, 0xffff3399, 0xffff33cc, 0xffff33ff, 0xffff6600, 0xffff6633, 0xffff6666, 0xffff6699, 0xffff66cc, 0xffff66ff, 0xffff9900, 0xffff9933,
	0xffff9966, 0xffff9999, 0xffff99cc, 0xffff99ff, 0xffffcc00, 0xffffcc33, 0xffffcc66, 0xffffcc99, 0xffffcccc, 0xffffccff, 0xffffff00, 0xffffff33, 0xffffff66, 0xffffff99, 0xffffffcc, 0xffffffff,
	// 4 цвета 16 по 4
	0xff000000, 0xff0000ff, 0xff00ff00, 0xffff0000, 0xff000000, 0xffffff00, 0xffff00ff, 0xffff0000, 0xff000000, 0xff00ffff, 0xff0000ff, 0xffff00ff, 0xff000000, 0xff00ff00, 0xff00ffff, 0xffffff00,
	0xff000000, 0xffff00ff, 0xff00ffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffbf0000, 0xff9f0000, 0xffff0000, 0xff000000, 0xffbfff00, 0xff9fff00, 0xffffff00,
	0xff000000, 0xffbf00ff, 0xff9f00ff, 0xffff00ff, 0xff000000, 0xff9fff00, 0xff9f00ff, 0xff9f0000, 0xff000000, 0xffb8ff00, 0xffbf00ff, 0xffbf0000, 0xff000000, 0xff00ffff, 0xffffff00, 0xffff0000,
	0xff000000, 0xffff0000, 0xff00ff00, 0xff00ffff, 0xff000000, 0xff00ffff, 0xffffff00, 0xffffffff, 0xff000000, 0xffffff00, 0xff00ff00, 0xffffffff, 0xff000000, 0xff00ffff, 0xff00ff00, 0xffffffff,
	// 16 цветов
	0xff000000, 0xff0000aa, 0xff00aa00, 0xff00aaaa, 0xffaa0000, 0xffaa00aa, 0xffaa5500, 0xffaaaaaa, 0xff555555, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
	// 2 цвета
	0xff000080, 0xffffffff
};


CAZBK::CAZBK()
	: m_pTexture(nullptr)
	, m_pAZBKSound(nullptr)
	, m_bA16M_Trigger(false)
	, m_bAZ50HzEnabled(false)
	, m_nRND(0x35355353)
	, m_nAltproMode(0)
	, m_nAltProMemBank(0)
{
	//на случай, если у вас UNIX-чё-то или компилятор не MinGW
	//std::random_device device;
	//m_rndEngine.seed(device());
	m_rndEngine.seed(std::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();
}

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

	if (m_pMemoryMap.data())
	{
		m_nMemorySize = AZBK_MEMSIZE; // количество памяти 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;
		}
	}
}

CString CAZBK::GetMemoryMapViewData(const int seg)
{
	CString strRet;
	if (seg < DEC_BANKS_NUM)
	{
		int sa = seg * 2; // начальный сегмент альтпро
		for (int j = 0; j < 2; ++j)
		{
			BKMEMBank_t &mb = m_pMemoryMap[sa];
			int n = 0;
			if (mb.bReadable) n |= 2;
			if (mb.bWritable) n |= 1;
			CString strTmp;
			strTmp.Format(g_arStrMMVw_AZ[n], mb.nBank);
			if (j)
			{
				strRet += _T('/');
			}
			strRet += strTmp;
			sa++;
		}

		return strRet;
	}

	return g_arStrMMVw_AZ[0];
}

// подключение страницы в окно.
// вход: 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(const_cast<uint8_t*>(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.nBLTC177270 = 0;
	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::SetSel(bool b)
{
	m_AZData.bSEL1 = b;
}

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 | m_pAZBlt->isRun() | m_pAZBlt->isReadCmds();
			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 = (8253729 * (m_nRND ^ m_rndEngine()) + 2396403);
			m_nRND = ((m_nRND >> 7) | (m_nRND << 22)) ^ m_rndEngine();
			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 & 0x1ff;
			return true;

		case REG_SCR_VSCRL_L1:
			m_AZRegs.nVSCRL177246 = value & 0x1ff;
			return true;

		case REG_SCR_VSCRL_L0:
			m_AZRegs.nVSCRL177250 = value & 0x1ff;
			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:
		{
			const uint16_t cmd = value & 0x7f;
			const uint16_t newval = value & (0x7f | AZ_BLT_MANUAL);

			if (cmd == 0) // если количество команд == 0
			{
				m_pAZBlt->Reset(); // то асинхронный ресет
				// задаём новое значение
				m_AZRegs.nBLTC177270 = newval;
			}
			else if (m_pAZBlt->isRun()) // если блиттер работает, то мы можем поменять только количество команд.
			{
				m_AZRegs.nBLTC177270 &= 0xff00;
				m_AZRegs.nBLTC177270 |= cmd;
			}
			else // если блиттер не работает
			{
				// если был установлен ручной режим, и мы запускаем вручную
				if ((m_AZRegs.nBLTC177270 & AZ_BLT_MANUAL) && (value & AZ_BLT_RUNMAN))
				{
					// запускаем
					m_pAZBlt->DoExec(cmd, AZ_BANK_TO_OFFSET(m_AZRegs.nBLTP177272));
				}
				// а теперь задаём новое значение.
				m_AZRegs.nBLTC177270 = newval;
			}
		}
			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))
			{
				m_bAZ50HzEnabled = true;
			}
			else
			{
				m_bAZ50HzEnabled = false;
			}

			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) // выключаем таймер 50 Гц
				{
					m_AZRegs.nAZ_mapper_ctrl &= ~(AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER); // чтобы нельзя было включить оба таймера одновременно
					m_bAZ50HzEnabled = false;
				}
				else // включаем таймер 50 Гц
				{
					m_AZRegs.nAZ_mapper_ctrl |= (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER); // чтобы нельзя было включить оба таймера одновременно
					m_bAZ50HzEnabled = true;
				}
			}

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

