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

#include "Platform.h"
#include "BK001001.h"
#include "Ini.h"
#include "ima.h"

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

const int CAZBK::m_arAZBPP[4] = { 1, 2, 4, 8 };
const int CAZBK::m_arAZLLEN[4] = { 32 * 2, 64 * 2, 128 * 2, 256 * 2 }; // ширина строки в байтах
const int CAZBK::m_arAZXS[4] = { 1, 2, 4, 8 };
const int CAZBK::m_arAZYS[4] = { 1, 2, 3, 4 };

const int CAZBK::m_arAZLSCRL[12] = { 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144 };

IMPLEMENT_DYNAMIC(CAZBK, CBKMPIDevice)

CAZBK::CAZBK()
	: azcmd(0)
	, azunit(0)
	, azblkn(0)
	, dr_rd_data(nullptr)
	, dr_wr_data(nullptr)
	, curr_rd_buf(nullptr)
	, dr_wcnt(0)
	, az_init_wcnt(256)
	, az_version(9)
	, m_nMaxUnit(0)
	, m_pUnits(nullptr)
	, m_bSEL1(false)
	, m_nBootUnit(-1)
	, m_azPalette { 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, 0x3E0,
	                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, 0x10, 0x1F, 0x200, 0x210, 0x3E0, 0x3FF, 0x4000, 0x4010,
	                0x4200, 0x4210, 0x6318, 0x7C00, 0x7C1F, 0x7FE0, 0x7FFF, 0x10, 0x7FFF }
	, m_nSCRCSR(012100)
	, m_nLegacyPalette(0)
	, m_nSCRPageNum(040000 / 010000)
	, m_nCVXLeft(0)
	, m_nCVXRight(0)
	, m_nCVXCSR(0)
	, m_pTexture(nullptr)
	, m_devMainBoard(BK_DEV_MPI::NONE)
	, m_pAZBKSound(nullptr)
{
	srand((unsigned)time(nullptr));
	g_Config.m_bEnableIRPS = false; // выключаем блок ИРПС, чтобы он своими регистрами не мешался.

	m_strConfImgPath = g_Config.GetConfCurrPath() + _T("AZBK\\");

	m_pAZIni = new CIni();

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

	m_pAZBKSound = new CBkSound();

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

CAZBK::~CAZBK()
{
	StopDMAThread();
	m_pAZIni->Clear();
	SAFE_DELETE(m_pAZBKSound);
	SAFE_DELETE(m_pAZIni);
	SAFE_DELETE_ARRAY(m_pUnits);
}

bool CAZBK::InitialSetDevice()
{
	SetScreenParam(m_nSCRCSR);

	m_pMemoryMap = new BKMEMBank_t[AZ_BANKS_NUM]; // здесь окно 4 кб, значит банков - 16

	if (m_pMemoryMap)
	{
		m_nMemorySize = 32 * 1024 * 1024; // количество памяти 32Мб
		InitMemory(AZBK_PORTSIO_AREA);
		MemoryManager();
		return true;
	}

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

bool CAZBK::InitMemory(uint16_t wHighLim)
{
	m_nBKPortsIOArea = wHighLim;
	SAFE_DELETE_ARRAY(m_pMemory);

	if (m_nMemorySize)
	{
		m_pMemory = new uint8_t[m_nMemorySize];

		if (m_pMemory)
		{
			ZeroMemory(m_pMemory, 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_nConnectMap = 0;
			m_nAccessMode = 0;
			m_nDisableConnect = 0177777;
			m_nMMUCSR = 0;
			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_nConnectMap & m)
		{
			m_pMemoryMap[i].bReadable = TRUE;

			if (64 <= m_pMemoryMap[i].nBank && m_pMemoryMap[i].nBank < 128)
			{
				m_pMemoryMap[i].bWritable = FALSE; // безусловно аппаратное ограничение
			}
			else
			{
				if (m_nAccessMode & 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(int nWnd, int nPage, bool bRW, bool bForce /*= false*/)
{
	int m = 1 << nWnd;
	m_nConnectMap |= 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 (64 <= m_pMemoryMap[nWnd].nBank && m_pMemoryMap[nWnd].nBank < 128)
	{
		m_pMemoryMap[nWnd].bWritable = FALSE; // безусловно аппаратное ограничение
	}
	else
	{
		if (bRW)
		{
			m_nAccessMode &= ~m;
			m_pMemoryMap[nWnd].bWritable = TRUE;
		}
		else
		{
			m_nAccessMode |= m;
			m_pMemoryMap[nWnd].bWritable = FALSE;
		}
	}

	if (bForce)
	{
		m_nDisableConnect &= ~m;
	}
}

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

	if ((dwFlg == INVALID_FILE_ATTRIBUTES) || !(dwFlg & FILE_ATTRIBUTE_DIRECTORY))
	{
		g_BKMsgBox.Show(_T("Не найден образ microSD карты."), MB_OK);
		return false;
	}

	CString strAZini = m_strConfImgPath + _T("AZ.INI");
	dwFlg = ::GetFileAttributes(strAZini);

	if (dwFlg == INVALID_FILE_ATTRIBUTES)
	{
		g_BKMsgBox.Show(_T("Не найден файл az.ini."), MB_OK);
		return false;
	}

	// читаем ини файл
	m_pAZIni->SetIniFileName(strAZini);
	// теперь надо загрузить в память нужные пзу.
	CString strKey, strValue;

	if (m_pAZIni->FindKeyStart(_T("ROM"), strKey, strValue))
	{
		do
		{
			// strKey - это номер слота. т.е. надо оттуда его выделить
			if (strKey.GetAt(0) == _T('R'))
			{
				const int nSlot = StrToLong(strKey.Mid(1));

				if (0 <= nSlot && nSlot < 64)
				{
					const int nOffset = 256 * 1024 + nSlot * 4 * 1024;
					// теперь надо выделить и сформировать путь.
					strValue.Replace(_T('/'), _T('\\'));

					if (strValue.Mid(0, 3) == _T("0:\\"))
					{
						CString strRomPath = m_strConfImgPath + strValue.Mid(3);
						CFile file;

						if (file.Open(strRomPath, CFile::modeRead))
						{
							auto len = static_cast<UINT>(file.GetLength());

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

							const UINT readed = file.Read(m_pMemory + nOffset, len);
							file.Close();

							if (readed < 020000)
							{
								// остаток забиваем нулями
								memset(m_pMemory + nOffset + readed, 0, 020000 - readed);
							}

							if (readed != len)
							{
								// не прочиталось. надо что-то делать
								ASSERT(false);
							}
						}
						else
						{
							CString strError(MAKEINTRESOURCE(IDS_ERROR_CANTOPENFILE));
							g_BKMsgBox.Show(strError + _T('\'') + strRomPath + _T('\''), MB_OK | MB_ICONSTOP);
						}
					}
				}
			}
		}
		while (m_pAZIni->FindKeyNext(strKey, strValue));
	}

	// первый проход, сперва узнаем количество дисков, и их номера
	m_nMaxUnit = 0;

	if (m_pAZIni->FindKeyStart(_T("disks"), strKey, strValue))
	{
		do
		{
			// strKey - это номер слота. т.е. надо оттуда его выделить
			if (strKey.GetAt(0) == _T('D'))
			{
				uint32_t nNum = StrToLong(strKey.Mid(1));

				if (m_nMaxUnit < nNum)
				{
					m_nMaxUnit = nNum;
				}
			}
		}
		while (m_pAZIni->FindKeyNext(strKey, strValue));
	}

	m_nMaxUnit++; // сделаем не номер последнего юнита, а их количество
	m_pUnits = new AZ_UNIT[m_nMaxUnit];

	if (m_pUnits)
	{
		// второй проход, заполняем массив юнитов
		if (m_pAZIni->FindKeyStart(_T("disks"), strKey, strValue))
		{
			do
			{
				// strKey - это номер слота. т.е. надо оттуда его выделить
				if (strKey.GetAt(0) == _T('D'))
				{
					const uint32_t nNum = StrToLong(strKey.Mid(1));

					if (nNum < m_nMaxUnit)
					{
						// теперь надо выделить и сформировать путь.
						strValue.Replace(_T('/'), _T('\\'));

						if (strValue.Mid(0, 3) == _T("0:\\"))
						{
							m_pUnits[nNum].strOrigPathName = strValue;
							m_pUnits[nNum].strFileName = m_strConfImgPath + strValue.Mid(3);
							CFileStatus fs;

							if (CFile::GetStatus(m_pUnits[nNum].strFileName, fs))
							{
								m_pUnits[nNum].nDate = (fs.m_ctime.GetDay() & 037) | ((fs.m_ctime.GetMonth() & 017) << 5) | ((fs.m_ctime.GetYear() - 1980) << 9);
								m_pUnits[nNum].nTime = (fs.m_ctime.GetHour() << 11) | (fs.m_ctime.GetMinute() << 5) | ((fs.m_ctime.GetSecond() / 2) & 037);
								m_pUnits[nNum].nAttr = fs.m_attribute;
								m_pUnits[nNum].bRO = !!(fs.m_attribute & CFile::Attribute::readOnly);
								m_pUnits[nNum].nSize = fs.m_size;
								m_pUnits[nNum].nBlkSize = (fs.m_size + 0777) >> 9; //размер в блоках. блок - 512 байт
							}
						}
					}
				}
			}
			while (m_pAZIni->FindKeyNext(strKey, strValue));
		}
	}
	else
	{
		// TODO
		ASSERT(false);
	}

	// теперь определим загрузочный юнит.
	if (m_pAZIni->FindKeyStart(_T("boot"), strKey, strValue))
	{
		if (!strKey.IsEmpty() && strKey.GetAt(0) == _T('D'))
		{
			m_nBootUnit = StrToLong(strKey.Mid(1));
		}
	}

	// перманентно выключаем ПЗУ.
	register const auto brd = g_Platform.GetBoardPtr();
	m_devMainBoard = brd->GetBoardModel(); // это нечестно, но пусть пока так определим тип модели БК
	brd->BasicRomOFF(true);
	brd->Mon10RomOFF(true);
	brd->BOS11RomOFF(true);
	//brd->RAM11Wn1OFF(true); // это в AZBK не применяется, там через запись в 177716.
	// Но это нифига не надёжно. это поломается как только любая программа начнёт сама
	// переключать страницы
	return true;
}

/////////////////////////////////////////////////////////////////////////////////
// это ресет оборудования, выполняемый по команде RESET. вызывается из CPU
void CAZBK::OnReset()
{
	// пока не ясно, что делается
	azcs = 0;
	azdr = 0;
	azbr1 = 0;
	azbr2 = 0;
	m_nConnectMap = 0;
	m_nAccessMode = 0;
	m_nDisableConnect = 0177777;
	m_nMMUCSR = 0;
	m_nLegacyPalette = 0;
	m_nLegacyScrBuf = 0;
	m_nLegacyPgNum0 = 1;
	m_nLegacyPgNum1 = 0;

	// подключаем стартовое ПЗУ
	MapToWindow(15, 0100, false, true);
	m_bSEL1 = true;
}

void CAZBK::NextTick()
{
	m_bSEL1 = false;
}

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

	switch (addr & 0177776)
	{
		case REG_DMA_CSR:
			v = m_nDMACSR;
			break;

		case REG_DMA_MEM:
			v = m_nDMAStartPage;
			break;

		case REG_DMA_LEN_HI:
			v = (m_nDMAFileSize >> 16) & 0xff;
			break;

		case REG_DMA_LEN_LO:
			v = m_nDMAFileSize & 0x0000ffff;
			break;

		case REG_DMA_PGNUM:
			v = m_nDMACurrentPage;
			break;

		case REG_CVX_L16:
			v = m_nCVXLeft;
			break;

		case REG_CVX_R16:
			v = m_nCVXRight;
			break;

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

		case REG_CVX_LR8:
			if (dwFlags & GSSR_BYTEOP)
			{
				v = m_nCVXLeft >> 8;
			}
			else
			{
				v = (m_nCVXLeft >> 8) | (m_nCVXRight & 0177400);
			}

			break;

		case REG_CVX_CSR:
			v = m_nCVXCSR;
			break;

		case REG_CSR:
			v = azcs;
			break;

		case REG_DR:
			if (dwFlags & GSSR_INTERNAL)
			{
				v = azdr;
			}
			else
			{
				if (!(azcs & CS_DONE))
				{
					return false;
				}

				v = az_read_data(&azdr, &dr_rd_data, &dr_wcnt);
			}

			break;

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

			v = 0;
			break;

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

			v = 0776;
			break;

		case REG_SCR_CSR:
			v = m_nSCRCSR;
			break;

		case REG_SCR_PGNUM:
			v = m_nSCRPageNum;
			break;

		case REG_PAL_CELL:
			v = m_nPalCell;
			break;

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

		case REG_SCROLL:
			v = m_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 int n = (addr - REG_MMU_PG00) >> 1; //получим номер окна
			v = m_pMemoryMap[n].nBank;
		}
		break;

		case REG_MMU_MNT:
			v = m_nConnectMap;
			break;

		case REG_MMU_ACS:
			v = m_nAccessMode;
			break;

		case REG_MMU_DISA:
			v = m_nDisableConnect;
			break;

		case REG_MMU_CSR:
			v = m_nMMUCSR;
			break;

		case REG_VERS:
			v = az_version;
			break;

		case REG_RND:
			v = (rand() >> 5) & 0xffff;
			break;

		case REG_TKS:
			v = m_nCOM177560;
			break;

		case REG_TKB:
			v = m_nCOM177562;
			break;

		case REG_TPS:
			v = m_nCOM177564;
			break;

		case REG_TPB:
			v = m_nCOM177566;
			break;

		case 0177716:
			if (m_bSEL1)
			{
				v = 0170000;
			}
			else
			{
				return false;
			}

			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(register const uint16_t addr, register uint16_t value, register UINT dwFlags /*= 0*/)
{
	if (dwFlags & GSSR_BYTEOP)
	{
		value &= 0377; // запись в младший байт - как слово с обнулённым старшим байтом

		if (addr & 1)
		{
			//value <<= 8;
			return true; // запись в старший байт игнорируется.
		}
	}

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

		case REG_DMA_MEM:
			m_nDMAStartPage = value;
			return true;

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

		case REG_DMA_LEN_LO:
			m_nDMAFileSize = (m_nDMAFileSize & 0x00ff0000) | value;
			return true;

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

		case REG_CVX_L16:
			m_nCVXLeft = value;

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

			return true;

		case REG_CVX_R16:
			m_nCVXRight = value;

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

			return true;

		case REG_CVX_M16:
			m_nCVXLeft = m_nCVXRight = value;

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

			return true;

		case REG_CVX_LR8:
			if (dwFlags & GSSR_BYTEOP)
			{
				m_nCVXLeft = m_nCVXRight = value << 8;
			}
			else
			{
				m_nCVXLeft = value << 8;
				m_nCVXRight = value & 0xff00;
			}

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

			return true;

		case REG_CVX_CSR:
			m_nCVXCSR = value & 0x7;
			return true;

		case REG_CSR:
			azcs &= ~CS_WR_MASK;
			azcs |= value & CS_WR_MASK;
			az_start_io();
			return true;

		case REG_DR:
			if (!(azcs & CS_DONE))
			{
				return false;
			}

			az_write_data(&azdr, value, &dr_wr_data, &dr_wcnt);
			return true;

		case REG_BR1: // на БК не используется
			if (!(azcs & CS_DONE))
			{
				return false;
			}

			return true;

		case REG_BR2: // на БК не используется
			if (!(azcs & CS_DONE))
			{
				return false;
			}

			return true;

		case REG_SCR_CSR:
			m_nSCRCSR = value;
			SetScreenParam(value);
			return true;

		case REG_SCR_PGNUM:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_nSCRPageNum = value & 0017777;
		}
			return true;

		case REG_PAL_CELL:
			m_nPalCell = value & 0x1ff;
			return true;

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

			return true;

		case REG_SCROLL:
			m_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 int n = (addr - REG_MMU_PG00) >> 1; //получим номер окна
			m_pMemoryMap[n].nBank = value & 0017777;
			m_pMemoryMap[n].nOffset = AZ_BANK_TO_OFFSET(m_pMemoryMap[n].nBank);
		}

			MemoryManager();
		return true;

		case REG_MMU_MNT:
			m_nConnectMap = value;
			MemoryManager();
			return true;

		case REG_MMU_ACS:
			m_nAccessMode = value;
			MemoryManager();
			return true;

		case REG_MMU_DISA:
			m_nDisableConnect = value;
			//MemoryManager();
			return true;

		case REG_MMU_CSR:
			m_nMMUCSR = value;
			//MemoryManager();
			return true;

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

		// заглушки
		case 0177130:
		case 0177132:
			return true;

		case REG_TKS:
			m_nCOM177560 = value;
			return true;

		case REG_TKB:
			m_nCOM177562 = value;
			return true;

		case REG_TPS:
			m_nCOM177564 = value;
			return true;

		case REG_TPB:
			m_nCOM177566 = value;
			return true;

		case 0177662:
		{
			std::lock_guard<std::mutex> lk(m_mutScrParam);
			m_nLegacyPalette = (value >> 8) & 0xf;
			m_nLegacyScrBuf = (value & 0100000) ? 1 : 0;
			m_nSCRPageNum = m_nLegacyScrBuf ? 040000 * 7 / 010000 : 040000 / 010000;
		}
			return false; // RPLY не выдавать

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

		case 0177714:
			if (!(m_nCVXCSR & 2))
			{
				if (m_nCVXCSR & 1)
				{
					m_nCVXLeft = value << 8;
					m_nCVXRight = value & 0xff00;
				}
				else
				{
					m_nCVXLeft = m_nCVXRight = value << 8;
				}

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

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

		case 0177716:
			if ((value & 04000) && m_devMainBoard == BK_DEV_MPI::BK0011M)
			{
				m_nLegacyPgNum0 = (value >> 12) & 7;

				// стр 11 ПЗУ (контроллер альтпро использует этот сигнал, для отключения ОЗУ в окне 1.
				// страницы ПЗУ соответственно у него нету)
				if (value & 020)
				{
					m_nLegacyPgNum1 = 11;
				}
				// стр 10 ПЗУ
				else if (value & 010)
				{
					m_nLegacyPgNum1 = 10;
				}
				// стр 9 ПЗУ
				else if (value & 2)
				{
					m_nLegacyPgNum1 = 9;
				}
				// стр 8 ПЗУ
				else if (value & 1)
				{
					m_nLegacyPgNum1 = 8;
				}
				else
				{
					m_nLegacyPgNum1 = (value >> 8) & 7;
				}
			}
			else if (!(m_nCVXCSR & 4))
			{
				register uint16_t w = value & 0144; // получим маску звуковых битов

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

				m_nCVXLeft = m_nCVXRight = w << 9;

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

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

	return false;
}


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

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

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

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

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

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

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

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

void CAZBK::DropLegacyShadowMap(register const uint16_t addr, register uint16_t value, register UINT dwFlags/* = 0*/)
{
	int nOffset = 0;

	if (addr < 040000)
	{
		// страница 0.
		nOffset = 6 * 040000;
	}
	else if (addr < 0100000)
	{
		//окно 0
		nOffset = m_nLegacyPgNum0 * 040000;
	}
	else if (addr < 0140000)
	{
		//окно 1
		if (m_nLegacyPgNum1 < 8)
		{
			nOffset = m_nLegacyPgNum1 * 040000;
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}

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

bool CAZBK::SetByteT(register const uint16_t addr, register uint8_t value, int &nTC)
{
	register const int nBank = AZ_ADDRESS_TO_BANK(addr);
	register 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 (!(m_nDisableConnect & (1 << nBank)) && memoryBank.bWritable)
	{
		nTC = memoryBank.nTimingCorrection;
		m_pMemory[memoryBank.nOffset + (addr & 007777)] = value;
		return true;
	}

	nTC = 0;
	return false;
}

bool CAZBK::SetWordT(register const uint16_t addr, register uint16_t value, int &nTC)
{
	register const int nBank = AZ_ADDRESS_TO_BANK(addr);
	register 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 (!(m_nDisableConnect & (1 << nBank)) && memoryBank.bWritable)
	{
		nTC = memoryBank.nTimingCorrection;
		*(reinterpret_cast<uint16_t *>(&m_pMemory[memoryBank.nOffset + (addr & 007776)])) = value;
		return true;
	}

	nTC = 0;
	return false;
}

bool CAZBK::GetByteTInternal(register const uint16_t addr, register uint8_t *pValue, int &nTC)
{
	register const int nBank = AZ_ADDRESS_TO_BANK(addr);
	register 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 (!(m_nDisableConnect & (1 << nBank)) && memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = m_pMemory[memoryBank.nOffset + (addr & 007777)];
		return true;
	}

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

bool CAZBK::GetWordTInternal(register const uint16_t addr, register uint16_t *pValue, int &nTC)
{
	register const int nBank = AZ_ADDRESS_TO_BANK(addr);
	register 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 (!(m_nDisableConnect & (1 << nBank)) && memoryBank.bReadable)
	{
		nTC = memoryBank.nTimingCorrection;
		// если читать можно, заодно и ремап сделаем
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[memoryBank.nOffset + (addr & 007776)]));
		return true;
	}

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



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

	return pMsf->SetBlockBaseMemory(id, m_pMemory, m_nMemorySize);
}

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

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



/////////////////////////////////////////////////////////////////////////////////
//
// AZ Controller
//
/////////////////////////////////////////////////////////////////////////////////

void CAZBK::az_set_done()
{
	azcs |= CS_DONE;

	switch (azcmd)
	{
		case CMD_READ:
		case CMD_WRITE:
			if (azcs & CS_IE)
			{
				g_Platform.VectorInterrupt(CS_VECTOR); // устанавливаем прерывание
			}

			break;
	}
}

void CAZBK::az_start_io()
{
	uint32_t size;

	switch (AZ_CMD(azcs))
	{
		case CMD_RESET:

			// CLR_INT(AZ); // снимаем прерывание
			for (uint32_t unit = 0; unit < m_nMaxUnit; unit++)
			{
				if (m_pUnits[unit].fFile.m_hFile != CFile::hFileNull)
				{
					//m_pUnits[unit].fFile.Flush();
					m_pUnits[unit].fFile.Close();
				}
			}

			dr_rd_data = dr_wr_data = nullptr;
			az_init_wcnt = 256;
			dr_wcnt = 0;
			azblkn = 0;
			azcs = CS_DONE;
			return;
	}

	if (azcs & CS_DONE)
	{
		azcmd = AZ_CMD(azcs);
		azcs &= ~(CS_DONE | CS_BIG | CS_ERR | CS_CMD_MASK);

		if (azcmd != CMD_READ_BUF)
		{
			az_init_wcnt = 256;
		}

		switch (azcmd)
		{
			case CMD_SETUNI:
			case CMD_SETBLK:
			case CMD_SETBLKX:
				switch (azcmd)
				{
					case CMD_SETUNI:
						if (m_pUnits[azunit].fFile.m_hFile != CFile::hFileNull)
						{
							m_pUnits[azunit].fFile.Close();
						}

						azunit = azdr & AZ_UNIT_MASK;

						if (azunit < m_nMaxUnit)
						{
							azblkn = 0;
							UINT nFlags = (m_pUnits[azunit].bRO) ? CFile::modeRead : CFile::modeReadWrite;

							// хоть это и учтено далее, файл нулевого размера и не существующий файл
							// вообще не будем открывать
							if (m_pUnits[azunit].nSize)
							{
								m_pUnits[azunit].fFile.Open(m_pUnits[azunit].strFileName, nFlags | CFile::shareExclusive);
							}
							else
							{
								azcs |= CS_ERR;
							}
						}

						break;

					case CMD_SETBLK:
						azblkn = azdr & AZ_LSB_MASK;
						break;

					case CMD_SETBLKX:
						azblkn &= AZ_LSB_MASK;
						azblkn |= azdr * 65536;
						break;
				}

			/* FALLTHROUGH */
			case CMD_GET_SIZE:
			case CMD_GET_SIZEX:
			case CMD_READ:
			case CMD_WRITE:
				if (azunit >= m_nMaxUnit)
				{
					azcs |= CS_ERR;
					break;
				}

				size = m_pUnits[azunit].nBlkSize;

				if ((m_pUnits[azunit].fFile.m_hFile == CFile::hFileNull) ||
				        size <= azblkn)
				{
					azcs |= CS_ERR;
					break;
				}

				curr_rd_buf = azbuf;

				switch (azcmd)
				{
					case CMD_SETUNI:
						break;

					case CMD_GET_SIZE:
						if (size > 65535)
						{
							azbuf[0] = 65535;
							azcs |= CS_BIG;
						}
						else
						{
							azbuf[0] = size;
						}

						dr_rd_data = azbuf;
						dr_wr_data = nullptr;
						dr_wcnt = 1;
						break;

					case CMD_GET_SIZEX:
						azbuf[0] = size & AZ_LSB_MASK;
						azbuf[1] = size >> 16;
						dr_rd_data = azbuf;
						dr_wr_data = nullptr;
						dr_wcnt = 2;
						break;

					case CMD_READ:  // прочитать в буфер блок
					{
						LONGLONG seek = LONGLONG(azblkn) * 512;

						if (m_pUnits[azunit].fFile.Seek(seek, CFile::SeekPosition::begin) == seek)
						{
							UINT rd = AZ_BUF_SIZE * 2;

							if (m_pUnits[azunit].fFile.Read(azbuf, rd) == rd)
							{
								break;
							}
						}

						azcs |= CS_ERR;
					}
					break;

					case CMD_WRITE: // записать блок из буфера
						if (!m_pUnits[azunit].bRO)
						{
							LONGLONG seek = LONGLONG(azblkn) * 512;

							if (m_pUnits[azunit].fFile.Seek(seek, CFile::SeekPosition::begin) == seek)
							{
								m_pUnits[azunit].fFile.Write(azbuf, AZ_BUF_SIZE * 2);
								break;
							}
						}

						azcs |= CS_ERR;
						break;
				}

				break;

			case CMD_READ_BUF:
				dr_wr_data = nullptr;
				dr_rd_data = curr_rd_buf;
				dr_wcnt = az_init_wcnt;
				break;

			case CMD_WRITE_BUF:
				dr_wr_data = azbuf;
				dr_rd_data = nullptr;
				dr_wcnt = az_init_wcnt;
				memset(azbuf, 0, sizeof(azbuf));
				break;

			case CMD_NET:
				if (azcs & CS_IE)
				{
					azcs &= ~CS_IE;
					return;
				}

			/* FALLTHROUGH */
			case CMD_NOP:
				break;

			case CMD_HFS_GET_AZUNITS:
				az_fill_table();
				az_init_wcnt = sizeof(az_assets) / 2;
				curr_rd_buf = reinterpret_cast<uint16_t *>(az_assets);
				break;

			case CMD_GET_FEAT:
				azbuf[0] = az_version;
				azbuf[1] = m_nMaxUnit - 1;
				azbuf[2] = 0;
				dr_wr_data = nullptr;
				dr_rd_data = azbuf;
				dr_wcnt = 3;
				break;

			/*FALLTHROUGH*/
			default:
				azcs |= CS_ERR;
				break;
		}

		az_set_done();
	}

	azcs &= CS_RD_MASK;
}

void CAZBK::az_fill_table()
{
	memset(az_assets, 0, sizeof(az_assets));

	for (uint32_t unit = 0; unit < m_nMaxUnit; ++unit)
	{
		if (m_pUnits[unit].nSize == 0 || m_pUnits[unit].strFileName.IsEmpty())
		{
			continue;
		}

		/* XXX: endian */
		az_assets[unit].size = m_pUnits[unit].nBlkSize;
		az_assets[unit].date = m_pUnits[unit].nDate;
		az_assets[unit].time = m_pUnits[unit].nTime;
		az_assets[unit].flag = UF_CFG | UF_MTD;
		az_assets[unit].attr = m_pUnits[unit].nAttr & 0xff;
		::WideCharToMultiByte(CP_ACP, 0, m_pUnits[unit].strOrigPathName.GetString(), -1, reinterpret_cast<LPSTR>(az_assets[unit].name), 130, nullptr, nullptr);
	}
}


uint16_t CAZBK::az_read_data(uint16_t *reg, uint16_t **buff, int *wcnt)
{
	uint16_t data = *reg;

	if (*buff != nullptr)
	{
		data = *reg = **buff;
		(*buff)++;

		if (--(*wcnt) == 0)
		{
			*buff = nullptr;
		}
	}

	return data;
}

void CAZBK::az_write_data(uint16_t *reg, uint16_t data, uint16_t **buff, int *wcnt)
{
	*reg = data;

	if (*buff != nullptr)
	{
		**buff = data;
		(*buff)++;

		if (--(*wcnt) == 0)
		{
			*buff = nullptr;
		}
	}
}


/////////////////////////////////////////////////////////////////////////////////
//
// AZ Screen
//
/////////////////////////////////////////////////////////////////////////////////

bool CAZBK::StartVideoThread()
{
	if (!m_lockVidThr.IsLocked())
	{
		m_VideoThread = std::thread(&CAZBK::VideoThreadFunc, this);

		if (m_VideoThread.joinable())
		{
			m_VideoThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::StopVideoThread()
{
	if (m_lockVidThr.IsLocked())
	{
		m_lockVid.Lock();

		do
		{
			SleepEx(10, FALSE);
		} while (m_lockVidThr.IsLocked());
	}
}

void CAZBK::VideoThreadFunc() const
{
	DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
	LARGE_INTEGER liDueTime;    //интервал таймера
	liDueTime.QuadPart = -10000000LL / 60;
	// создаём waitable timer.
	HANDLE hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr);

	if (hWaitableTimer)
	{
		m_lockVidThr.Lock();
		// Устанавливаем таймер.
		::SetWaitableTimer(hWaitableTimer, &liDueTime, 17, nullptr, nullptr, TRUE);

		do
		{
			FillTexture();
			g_Platform.GetMainPtr()->PostMessage(WM_SCR_DRAW, 1);
			// ждём срабатывания таймера.
			::WaitForSingleObject(hWaitableTimer, INFINITE);
		} while (!m_lockVid.IsLocked());     //пока не придёт событие остановки

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

	::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}


uint32_t CAZBK::R5G5B5toRGB(uint16_t c) const
{
	register uint8_t b = (c & 0x1f) << 3;
	register uint8_t g = ((c >> 5) & 0x1f) << 3;
	register uint8_t r = ((c >> 10) & 0x1f) << 3;
	return 0xff000000 | (uint32_t(r) << 16) | (uint32_t(g) << 8) | b;
}

uint16_t CAZBK::RGBtoR5G5B5(uint32_t c) const
{
	register uint8_t b = (c & 0xff) >> 3;
	register uint8_t g = ((c >> 8) & 0xff) >> 3;
	register uint8_t r = ((c >> 16) & 0xff) >> 3;
	return (uint16_t(r) << 10) | (uint16_t(g) << 5) | b;
}

void CAZBK::SetScreenParam(uint16_t v)
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);

	m_nAZBpp = m_arAZBPP[v & 3];
	m_nAZLineLen = m_arAZLLEN[(v >> 3) & 3];
	m_nAZXStretch = m_arAZXS[(v >> 6) & 3];
	m_nAZYStretch = m_arAZYS[(v >> 9) & 3];
	int idx = (v >> 12) & 0x0f;

	if (idx > 11)
	{
		idx = 11;
	}

	m_nAZScrlSize = m_arAZLSCRL[idx];
	// теперь посчитаем сколько строк будет рулон
	m_nAZScrlLines = (m_nAZScrlSize * 2 / m_nAZLineLen);
}

// формирование экрана
void CAZBK::FillTexture() const
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);
	register uint32_t *pBits = m_pTexture;
	register const int nOffs = AZ_BANK_TO_OFFSET(m_nSCRPageNum); // это смещение до начала экрана в памяти
	register uint8_t *pDataBase = (&m_pMemory[nOffs]); // вот отсюда начинаются данные

	switch (m_nAZBpp)
	{
		default:
		case 1:
		{
			register int nHeight = 768; // фиксированная реальная высота в строках
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_2_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_nAZLineLen * ((nLinesCount + m_nSCRL177250) % m_nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 8; ++i) // 8 бит
					{
						register const size_t p = w & 1; w >>= 1;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px; // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl1; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl1:
				--nHeight; nLinesCount++;
				register int sy = m_nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 2:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_4_COLOR_IDX + m_nLegacyPalette * 4];
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_nAZLineLen * ((nLinesCount + m_nSCRL177250) % m_nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 4; ++i) // 4 двухбития
					{
						register const size_t p = w & 0x3; w >>= 2;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl2; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl2:
				--nHeight; nLinesCount++;
				register int sy = m_nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 4:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_16_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + size_t(m_nAZLineLen) * ((nLinesCount + m_nSCRL177250) % m_nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 2; ++i)    // 2 четырёхбития
					{
						register const size_t p = w & 0xf; w >>= 4;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl4; // если нарисовали всю строку, то хватит
							}
						}
						while (--sx);
					}
				}

l_endl4:
				--nHeight; nLinesCount++;
				register int sy = m_nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;

		case 8:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_nAZLineLen * ((nLinesCount + m_nSCRL177250) % m_nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_nAZLineLen; ++c) // длина линии в байтах
				{
					register uint32_t p = *pData++; // берём очередной байт
					register const uint32_t px = *(pPaletteIdx + p);
					register int sx = m_nAZXStretch; // повторим точку нужное количество раз

					do
					{
						*pBits++ = px;  // рисуем точку

						if (!(--nWidth))
						{
							goto l_endl8; // если нарисовали всю строку, то хватит
						}
					}
					while (--sx);
				}

l_endl8:
				--nHeight; nLinesCount++;
				register int sy = m_nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			}
			while (nHeight > 0);
		}
		break;
	}
}

/////////////////////////////////////////////////////////////////////////////////
//
// AZ DMA Sound
//
/////////////////////////////////////////////////////////////////////////////////

void CAZBK::SetDMAWork()
{
	if (m_nDMACSR & (1 << 2))
	{
		StopDMAThread();
	}
	else if (m_nDMACSR & (1 << 0))
	{
		StartDMAThread();
	}
}

void CAZBK::StopDMAThread()
{
	if (m_lockDMAThr.IsLocked()) // если поток запущен
	{
		m_lockDMA.Lock();

		do
		{
			SleepEx(10, FALSE);
		} while (m_lockDMAThr.IsLocked());
	}
}

bool CAZBK::StartDMAThread()
{
	if (!m_lockDMAThr.IsLocked()) // если поток ещё не запущен
	{
		m_DMAThread = std::thread(&CAZBK::DMAThreadFunc, this);

		if (m_DMAThread.joinable())
		{
			m_DMAThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::DMAThreadFunc() const
{
	m_lockDMAThr.Lock();

	int nSSRnum = (m_nDMACSR >> 6) & 7;
	int nSSR = 0;
	switch (nSSRnum)
	{
		default:
		case 0:
			nSSR = 44100;
			break;
	}

	m_pAZBKSound->ReInit(nSSR);

	int nMode = (m_nDMACSR >> 9) & 7; // режим интерпретации

	uint32_t nBufSizePCM = m_pAZBKSound->getBufSizeAZ();
	uint32_t nBufSizeIma = nBufSizePCM / 4;
	auto pBufPCM = new uint8_t[nBufSizePCM];
	auto pBufPCM2 = reinterpret_cast<SAMPLE_IO *>(pBufPCM);
	auto pBufIMA = new uint8_t[nBufSizeIma];

	ima_state state[2];
	memset(&state, 0, sizeof(state));

	uint32_t nSndPtr = 0;
	uint32_t nImaPtr = 0;
	uint32_t nPCMPtr = 0;
	uint32_t nDataPtr = 0;
	m_nDMACurrentPage = m_nDMAStartPage;
	auto pBufData = m_pMemory + AZ_BANK_TO_OFFSET(size_t(m_nDMACurrentPage));

	if (nMode==0)
	{
		for (;;)
		{
			if (m_lockDMA.IsLocked())	//событие остановки
			{
				break;
			}

			pBufPCM[nPCMPtr++] = pBufData[nDataPtr++]; nSndPtr++; // берём очередной байт
			pBufPCM[nPCMPtr++] = pBufData[nDataPtr++]; nSndPtr++; // одно слово

			if (nPCMPtr >= nBufSizePCM)
			{
				// пришло время отправить буфер звучать
				m_pAZBKSound->SoundGen_FeedDAC_MixerAZ(pBufPCM2);
			}

			if (nDataPtr >= 010000)
			{
				// пришло время переходить к следующему сегменту
				nDataPtr = 0;
				if (nSndPtr >= m_nDMAFileSize*2)
				{
					nSndPtr = 0;
					m_nDMACurrentPage = m_nDMAStartPage;
					bool bLoop = !(m_nDMACSR & (1 << 3)) ? !(m_nDMACSR & (1 << 1)) : false;
					if (!bLoop)
					{
						m_nDMACSR |= (1 << 3);
						break;
					}
				}
				else
				{
					m_nDMACurrentPage++;
				}

				if (!(m_nDMACSR & (1 << 0)))
				{
					break;
				}

				pBufData = m_pMemory + AZ_BANK_TO_OFFSET(size_t(m_nDMACurrentPage));
			}
		}
	}
	else
	{
		for (;;)
		{
			if (m_lockDMA.IsLocked())	//событие остановки
			{
				break;
			}

			pBufIMA[nImaPtr++] = pBufData[nDataPtr++]; nSndPtr++; // берём очередной байт

			if (nImaPtr >= nBufSizeIma)
			{
				nImaPtr = 0;
				// пришло время декодировать буфер и отправить его звучать
				switch (nMode)
				{
					case 4:
						ima_decode_mono(state, pBufPCM2, pBufIMA, nBufSizeIma);
						break;
					case 5:
						ima_decode_stereo(state, pBufPCM2, pBufIMA, nBufSizeIma);
						break;
					default:
						memset(pBufPCM, 0, nBufSizePCM);
						break;
				}
				m_pAZBKSound->SoundGen_FeedDAC_MixerAZ(pBufPCM2);
			}

			if (nDataPtr >= 010000)
			{
				// пришло время переходить к следующему сегменту
				nDataPtr = 0;
				if (nSndPtr >= m_nDMAFileSize*2)
				{
					nSndPtr = 0;
					m_nDMACurrentPage = m_nDMAStartPage;
					bool bLoop = !(m_nDMACSR & (1 << 3)) ? !(m_nDMACSR & (1 << 1)) : false;
					if (!bLoop)
					{
						m_nDMACSR |= (1 << 3);
						break;
					}

					if (!(m_nDMACSR & (1 << 4)))
					{
						memset(&state, 0, sizeof(state));
					}
				}
				else
				{
					m_nDMACurrentPage++;
				}

				if (!(m_nDMACSR & (1 << 0)))
				{
					break;
				}

				pBufData = m_pMemory + AZ_BANK_TO_OFFSET(size_t(m_nDMACurrentPage));
			}
		}
	}
	delete[] pBufPCM;
	delete[] pBufIMA;

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

