// MSFManager.cpp: implementation of the CMSFManager class.
//


#include "pch.h"
#include "MSFManager.h"
#include "BKMessageBox.h"

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

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

#pragma warning(disable:4996)

#undef MSFLOGOUT
//#define MSFLOGOUT 1

#if defined MSFLOGOUT && _DEBUG
#define LOG_WRMSF _T("msf_write.txt")
#define LOG_RDMSF _T("msf_read.txt")
FILE *logFile = nullptr;
#endif

CMSFManager::CMSFManager()
	: m_pMSFFile(nullptr)
	, m_bOpenForLoad(true)
	, m_header({ MSF_STATE_ID, MSF_VERSION_CURRENT, 0 })
, m_nScanTableSize(0)
{
}

CMSFManager::~CMSFManager()
{
	ReleaseFile();
}


bool CMSFManager::ReleaseFile()
{
	if (m_pMSFFile) // если файл был открыт
	{
#if defined MSFLOGOUT && _DEBUG
		fclose(logFile);
#endif
		fclose(m_pMSFFile); // закрываем
		m_pMSFFile = nullptr;
		m_blocks.clear();
		return true;
	}

	return false;
}

bool CMSFManager::Seek(size_t nOffset, UINT nFrom) const
{
	return (fseek(m_pMSFFile, nOffset, nFrom) == 0);
}

// проверка msf файла
bool CMSFManager::CheckFile(const fs::path &strPath, bool bSilent)
{
	bool bRet = true;
	ReleaseFile();
	m_pMSFFile = _tfopen(strPath.c_str(), _T("rb"));

	if (m_pMSFFile) // если файл открылся
	{
		m_bOpenForLoad = true;

		// прочитаем заголовок
		if (fread(&m_header, 1, sizeof(m_header), m_pMSFFile) == sizeof(m_header))
		{
			size_t nFilePos = sizeof(m_header); // смещение в файле

			if (m_header.type == MSF_STATE_ID) // если ид верный
			{
				if (m_header.version > MSF_VERSION_CURRENT)
				{
					if (!bSilent)
					{
						g_BKMsgBox.Show(IDS_BK_ERROR_MSFVERSIONMISMATCH, MB_OK);
					}

					bRet = false;
				}
				else
				{
					// если старый msf файл
					if (m_header.version < MSF_VERSION_MINIMAL && !bSilent)
					{
						// выдадим сообщение
						g_BKMsgBox.Show(IDS_BK_ERROR_MSFOLDVERSION, MB_OK);
					}

					MSF_BLOCK_INFO bi{};

					// начинаем читать заголовки блоков
					while (!feof(m_pMSFFile))
					{
						// сперва прочитаем старый заголовок
						if (fread(&bi.header, 1, sizeof(MSF_BLOCK_HEADER), m_pMSFFile) == sizeof(MSF_BLOCK_HEADER))
						{
							uint32_t nHdrSize = 0;

							// если это расширенный заголовок
							if (static_cast<uint32_t>(bi.header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK)
							{
								// то дочитаем остальные данные
								if (fread(&bi.header.id, 1, sizeof(MSF_BLOCK_HEADEREX) - sizeof(MSF_BLOCK_HEADER), m_pMSFFile) != sizeof(MSF_BLOCK_HEADEREX) - sizeof(MSF_BLOCK_HEADER))
								{
									if (!bSilent)
									{
										g_BKMsgBox.Show(IDS_BK_ERROR_MSFCORRUPTMSF, MB_OK);
									}

									bRet = false; // если прочитать не удалось, значит ошибка
									break;
								}

								nHdrSize = sizeof(MSF_BLOCK_HEADEREX);
							}
							else
							{
								bi.header.id = uint32_t(-1);
								nHdrSize = sizeof(MSF_BLOCK_HEADER);
							}

							bi.offset = nFilePos;       // заполняем смещение
							m_blocks.push_back(bi);     // добавляем блок в список найденных блоков

							// переместимся к следующему блоку
							if (!Seek(bi.header.hdr.length - nHdrSize, SEEK_CUR))
							{
								if (!bSilent)
								{
									g_BKMsgBox.Show(IDS_BK_ERROR_MSFCORRUPTMSF, MB_OK);
								}

								bRet = false;
								break;
							}

							nFilePos += bi.header.hdr.length; // скорректируем смещение
						}
					}
				}
			}
			else
			{
				if (!bSilent)
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_MSFNOMSFFILE, MB_OK);
				}

				bRet = false;
			}
		}
		else
		{
			if (!bSilent)
			{
				g_BKMsgBox.Show(IDS_BK_ERROR_MSFREADHEADERRROR, MB_OK);
			}

			bRet = false;
		}

		ReleaseFile(); // закрываем файл и очищаем список блоков
	}
	else
	{
		if (!bSilent)
		{
			g_BKMsgBox.Show(IDS_BK_ERROR_MSFOPENFILEERROR, MB_OK);
		}

		bRet = false;
	}

	return bRet;
}

bool CMSFManager::OpenFile(const fs::path &strPath, bool bLoad, bool bSilent)
{
	bool bRet = true;
	ReleaseFile();
	CString strFlag = bLoad ? _T("rb") : _T("w+b");
	m_pMSFFile = _tfopen(strPath.c_str(), strFlag);

	if (m_pMSFFile) // если файл открылся
	{
		m_bOpenForLoad = bLoad;

		if (bLoad)
		{
			// если открываем для чтения
#if defined MSFLOGOUT && _DEBUG
			logFile = _tfopen(LOG_RDMSF, _T("w+t"));
#endif

			// прочитаем заголовок
			if (fread(&m_header, 1, sizeof(m_header), m_pMSFFile) == sizeof(m_header))
			{
#if defined MSFLOGOUT && _DEBUG
				_ftprintf(logFile, _T("Прочитан заголовок\n"));
				_ftprintf(logFile, _T("Тип: %d Версия: %d\n"), m_header.type, m_header.version);
				fflush(logFile);
#endif
				size_t nFilePos = sizeof(m_header);

				if (m_header.type == MSF_STATE_ID)
				{
#if defined MSFLOGOUT && _DEBUG
					_ftprintf(logFile, _T("Заголовок корректный.\n"));
					fflush(logFile);
#endif

					if (m_header.version > MSF_VERSION_CURRENT)
					{
#if defined MSFLOGOUT && _DEBUG
						_ftprintf(logFile, _T("Версия из будущего. Неизвестно как обрабатывать\n"));
						fflush(logFile);
#endif

						if (!bSilent)
						{
							g_BKMsgBox.Show(IDS_BK_ERROR_MSFVERSIONMISMATCH, MB_OK);
						}

						bRet = false;
					}
					else
					{
//                      // если старый msf файл
//                      if (m_header.version < MSF_VERSION_MINIMAL && !bSilent)
//                      {
// #if defined MSFLOGOUT && _DEBUG
//                          _ftprintf(logFile, _T("Версия старая, но поддерживаемая.\n"));
//                          fflush(logFile);
// #endif
//                          // выдадим сообщение
//                          g_BKMsgBox.Show(IDS_BK_ERROR_MSFOLDVERSION, MB_OK);
//                      }
#if defined MSFLOGOUT && _DEBUG
						_ftprintf(logFile, _T("Версия актуальная.\n"));
						fflush(logFile);
#endif
						MSF_BLOCK_INFO bi {};

						// начинаем читать заголовки блоков
						while (!feof(m_pMSFFile))
						{
							// сперва прочитаем старый заголовок
							if (fread(&bi.header, 1, sizeof(MSF_BLOCK_HEADER), m_pMSFFile) == sizeof(MSF_BLOCK_HEADER))
							{
								uint32_t nHdrSize = 0;

								// если это расширенный заголовок
								if (static_cast<uint32_t>(bi.header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK)
								{
#if defined MSFLOGOUT && _DEBUG
									_ftprintf(logFile, _T("Расширенный заголовок.\n"));
									fflush(logFile);
#endif

									// то дочитаем остальные данные
									if (fread(&bi.header.id, 1, sizeof(MSF_BLOCK_HEADEREX) - sizeof(MSF_BLOCK_HEADER), m_pMSFFile) != sizeof(MSF_BLOCK_HEADEREX) - sizeof(MSF_BLOCK_HEADER))
									{
#if defined MSFLOGOUT && _DEBUG
										_ftprintf(logFile, _T("Ошибка чтения данных расширенного заголовка.\n"));
										fflush(logFile);
#endif

										if (!bSilent)
										{
											g_BKMsgBox.Show(IDS_BK_ERROR_MSFCORRUPTMSF, MB_OK);
										}

										bRet = false; // если прочитать не удалось, значит ошибка
										break;
									}

									nHdrSize = sizeof(MSF_BLOCK_HEADEREX);
								}
								else
								{
#if defined MSFLOGOUT && _DEBUG
									_ftprintf(logFile, _T("Обычный заголовок.\n"));
									fflush(logFile);
#endif
									bi.header.id = uint32_t(-1);
									bi.header.flags = 0;
									bi.header.unpSize = 0;
									nHdrSize = sizeof(MSF_BLOCK_HEADER);
								}

								bi.offset = nFilePos;   // заполняем смещение
								m_blocks.push_back(bi); // добавляем блок в список найденных блоков
#if defined MSFLOGOUT && _DEBUG
								_ftprintf(logFile, _T("Обнаружен блок\n"));
								_ftprintf(logFile, _T("Смещение: 0x%0X\n"), bi.offset);
								_ftprintf(logFile, _T("Тип: "));

								switch (bi.header.hdr.type)
								{
									default:
									case MSF_BLOCKTYPE_UNKNOWN:
										_ftprintf(logFile, _T("Неопознан\n"));
										break;

									case MSF_BLOCKTYPE_BASEMEMORY:
										_ftprintf(logFile, _T("Образ памяти\n"));
										break;

									case MSF_BLOCKTYPE_CPU_REGISTERS:
										_ftprintf(logFile, _T("Регистры CPU\n"));
										break;

									case MSF_BLOCKTYPE_PREVIEW:
										_ftprintf(logFile, _T("Предпросмотр\n"));
										break;

									case MSF_BLOCKTYPE_10EXT16:         // deprecated
										_ftprintf(logFile, _T("Устаревший 10EXT16\n"));
										break;

									case MSF_BLOCKTYPE_10EXT32:         // deprecated
										_ftprintf(logFile, _T("Устаревший 10EXT32\n"));
										break;

									case MSF_BLOCKTYPE_PLATFORM:
										_ftprintf(logFile, _T("Данные платформы\n"));
										break;

									case MSF_BLOCKTYPE_PORT_REGS:
										_ftprintf(logFile, _T("Данные регистров/портов\n"));
										break;

									case MSF_BLOCKTYPE_MEMMAP:
										_ftprintf(logFile, _T("Карта памяти\n"));
										break;

									case MSF_BLOCKTYPE_BASEMEMORY11M:   // deprecated
										_ftprintf(logFile, _T("Устаревший BASEMEMORY11M\n"));
										break;

									case MSF_BLOCKTYPE_MEMORY_SMK512:   // deprecated
										_ftprintf(logFile, _T("Устаревший MEMORY_SMK512\n"));
										break;

									case MSF_BLOCKTYPE_CONFIG:
										_ftprintf(logFile, _T("Текстовый конфиг\n"));
										break;

									case MSF_BLOCKTYPE_FRAMEDATA:
										_ftprintf(logFile, _T("Данные фрейма\n"));
										break;

									case MSF_BLOCKTYPE_WAVE:
										_ftprintf(logFile, _T("Wave data\n"));
										break;

									case MSF_BLOCKTYPE_BASEMEMORYEX:
										_ftprintf(logFile, _T("Образ памяти Расширенный\n"));
										break;

									case MSF_BLOCKTYPE_PREVIEWEX:
										_ftprintf(logFile, _T("Предпросмотр Расширенный\n"));
										break;

									case MSF_BLOCKTYPE_PORT_REGSEX:
										_ftprintf(logFile, _T("Данные регистров/портов Расширенный\n"));
										break;

									case MSF_BLOCKTYPE_MEMMAPEX:
										_ftprintf(logFile, _T("Карта памяти Расширенный\n"));
										break;

									case MSF_BLOCKTYPE_CONFIGEX:
										_ftprintf(logFile, _T("Текстовый конфиг Расширенный\n"));
										break;

									case MSF_BLOCKTYPE_CUSTOMEX:
										_ftprintf(logFile, _T("Кастомные данные Расширенный\n"));
										break;
								}

								_ftprintf(logFile, _T("Ид: %d, Размер: %d\n"), bi.header.id, bi.header.hdr.length);

								if (bi.header.hdr.type & MSF_BLOCKTYPE_EXT_MASK)
								{
									_ftprintf(logFile, _T("Упакован: "));

									if (bi.header.flags & MSF_EX_FLAG_COMPRESSED)
									{
										_ftprintf(logFile, _T("Да, Размер распакованных данных: %d\n"), bi.header.unpSize);
									}
									else
									{
										_ftprintf(logFile, _T("Нет\n"));
									}
								}

								_ftprintf(logFile, _T("\n"));
								fflush(logFile);
#endif

								if (!Seek(bi.header.hdr.length - nHdrSize, SEEK_CUR))
								{
#if defined MSFLOGOUT && _DEBUG
									_ftprintf(logFile, _T("Ошибка позиционирования.\n"));
									fflush(logFile);
#endif

									if (!bSilent)
									{
										g_BKMsgBox.Show(IDS_BK_ERROR_MSFCORRUPTMSF, MB_OK);
									}

									bRet = false;
									break;
								}

								nFilePos += bi.header.hdr.length;   // скорректируем смещение
							}
						}
					}
				}
				else
				{
#if defined MSFLOGOUT && _DEBUG
					_ftprintf(logFile, _T("Заголовок неверный.\n"));
					fflush(logFile);
#endif

					if (!bSilent)
					{
						g_BKMsgBox.Show(IDS_BK_ERROR_MSFNOMSFFILE, MB_OK);
					}

					bRet = false;
				}
			}
			else
			{
				if (!bSilent)
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_MSFREADHEADERRROR, MB_OK);
				}

				bRet = false;
			}
		}
		else
		{
#if defined MSFLOGOUT && _DEBUG
			logFile = _tfopen(LOG_WRMSF, _T("w+t"));
#endif

			// если открываем для записи
			// запишем заголовок
			if (fwrite(&m_header, 1, sizeof(m_header), m_pMSFFile) != sizeof(m_header))
			{
				if (!bSilent)
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_MSFWRITEHEADERRROR, MB_OK);
				}

				bRet = false;
			}

#if defined MSFLOGOUT && _DEBUG
			_ftprintf(logFile, _T("Создан заголовок\n"));
			_ftprintf(logFile, _T("Тип: %d Версия: %d\n"), m_header.type, m_header.version);
			fflush(logFile);
#endif
		}
	}
	else
	{
		if (!bSilent)
		{
			g_BKMsgBox.Show(IDS_BK_ERROR_MSFOPENFILEERROR, MB_OK);
		}

		bRet = false;
	}

	if (!bRet)
	{
		ReleaseFile();
	}

	return bRet;
}

// ищем нужный блок в массиве
bool CMSFManager::FindBlock(MSF_BLOCKTYPE blockType, uint32_t id, MSF_BLOCK_INFO *pBi) const
{
	for (const auto &block : m_blocks)
	{
		if (block.header.hdr.type == blockType)
		{
			if (id == uint32_t(-1) || ((static_cast<uint32_t>(blockType) & MSF_BLOCKTYPE_EXT_MASK) && id == block.header.id))
			{
				*pBi = block;
				return true;
			}
		}
	}

	return false;
}


bool CMSFManager::SetBlockHeader(MSF_BLOCK_INFO *pBi) const
{
#if defined MSFLOGOUT && _DEBUG
	_ftprintf(logFile, _T("\nСохраняем заголовок.\n"));
	_ftprintf(logFile, _T("Тип: "));

	switch (pBi->header.hdr.type)
	{
		default:
		case MSF_BLOCKTYPE_UNKNOWN:
			_ftprintf(logFile, _T("Неопознан\n"));
			break;

		case MSF_BLOCKTYPE_BASEMEMORY:
			_ftprintf(logFile, _T("Образ памяти\n"));
			break;

		case MSF_BLOCKTYPE_CPU_REGISTERS:
			_ftprintf(logFile, _T("Регистры CPU\n"));
			break;

		case MSF_BLOCKTYPE_PREVIEW:
			_ftprintf(logFile, _T("Предпросмотр\n"));
			break;

		case MSF_BLOCKTYPE_10EXT16:         // deprecated
			_ftprintf(logFile, _T("Устаревший 10EXT16\n"));
			break;

		case MSF_BLOCKTYPE_10EXT32:         // deprecated
			_ftprintf(logFile, _T("Устаревший 10EXT32\n"));
			break;

		case MSF_BLOCKTYPE_PLATFORM:
			_ftprintf(logFile, _T("Данные платформы\n"));
			break;

		case MSF_BLOCKTYPE_PORT_REGS:
			_ftprintf(logFile, _T("Данные регистров/портов\n"));
			break;

		case MSF_BLOCKTYPE_MEMMAP:
			_ftprintf(logFile, _T("Карта памяти\n"));
			break;

		case MSF_BLOCKTYPE_BASEMEMORY11M:   // deprecated
			_ftprintf(logFile, _T("Устаревший BASEMEMORY11M\n"));
			break;

		case MSF_BLOCKTYPE_MEMORY_SMK512:   // deprecated
			_ftprintf(logFile, _T("Устаревший MEMORY_SMK512\n"));
			break;

		case MSF_BLOCKTYPE_CONFIG:
			_ftprintf(logFile, _T("Текстовый конфиг\n"));
			break;

		case MSF_BLOCKTYPE_FRAMEDATA:
			_ftprintf(logFile, _T("Данные фрейма\n"));
			break;

		case MSF_BLOCKTYPE_WAVE:
			_ftprintf(logFile, _T("Wave data\n"));
			break;

		case MSF_BLOCKTYPE_BASEMEMORYEX:
			_ftprintf(logFile, _T("Образ памяти Расширенный\n"));
			break;

		case MSF_BLOCKTYPE_PREVIEWEX:
			_ftprintf(logFile, _T("Предпросмотр Расширенный\n"));
			break;

		case MSF_BLOCKTYPE_PORT_REGSEX:
			_ftprintf(logFile, _T("Данные регистров/портов Расширенный\n"));
			break;

		case MSF_BLOCKTYPE_MEMMAPEX:
			_ftprintf(logFile, _T("Карта памяти Расширенный\n"));
			break;

		case MSF_BLOCKTYPE_CONFIGEX:
			_ftprintf(logFile, _T("Текстовый конфиг Расширенный\n"));
			break;

		case MSF_BLOCKTYPE_CUSTOMEX:
			_ftprintf(logFile, _T("Кастомные данные Расширенный\n"));
			break;
	}

	_ftprintf(logFile, _T("Ид: %d, Размер: %d\n"), pBi->header.id, pBi->header.hdr.length);

	if (pBi->header.hdr.type & MSF_BLOCKTYPE_EXT_MASK)
	{
		_ftprintf(logFile, _T("Упакован: "));

		if (pBi->header.flags & MSF_EX_FLAG_COMPRESSED)
		{
			_ftprintf(logFile, _T("Да, Размер распакованных данных: %d\n"), pBi->header.unpSize);
		}
		else
		{
			_ftprintf(logFile, _T("Нет\n"));
		}
	}

	fflush(logFile);
#endif
	// если блок не расширенный, то размер заголовка обычный, иначе расширенный
	return SetBlockData(reinterpret_cast<uint8_t *>(&pBi->header),
	                    static_cast<uint32_t>(pBi->header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK ?
	                    sizeof(MSF_BLOCK_HEADEREX) : sizeof(MSF_BLOCK_HEADER));
}

bool CMSFManager::GetBlockHeader(MSF_BLOCK_INFO *pBi) const
{
	bool bRet = false;

	if (Seek(pBi->offset, SEEK_SET)) // сначала переместимся к началу блока
	{
		if (static_cast<uint32_t>(pBi->header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK)
		{
			// если расширенный заголовок
			MSF_BLOCK_HEADEREX hdr{};

			// затем прочитаем заголовок
			if (fread(&hdr, 1, sizeof(MSF_BLOCK_HEADEREX), m_pMSFFile) == sizeof(MSF_BLOCK_HEADEREX))
			{
				if (pBi->header == hdr)
				{
					bRet = true;
				}
			}
		}
		else
		{
			// если обычный заголовок
			MSF_BLOCK_HEADER hdr{};

			// затем прочитаем заголовок
			if (fread(&hdr, 1, sizeof(MSF_BLOCK_HEADER), m_pMSFFile) == sizeof(MSF_BLOCK_HEADER))
			{
				if (pBi->header.hdr == hdr)
				{
					bRet = true;
				}
			}
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_MSFSEEKERROR, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockData(const uint8_t *pBuff, UINT length) const
{
	if (length && pBuff)
	{
#if defined MSFLOGOUT && _DEBUG
		_ftprintf(logFile, _T("Сохраняем данные. Размер: %d\n"), length);
		fflush(logFile);
#endif
		return (fwrite(pBuff, 1, length, m_pMSFFile) == length);
	}

	return true;
}


bool CMSFManager::GetBlockData(uint8_t *pBuff, UINT length) const
{
	if (length && pBuff)
	{
		return (fread(pBuff, 1, length, m_pMSFFile) == length);
	}

	return true;
}


// проверка размера блока, перед его чтением. На тот случай, если я опять наизменял конфигурацию
// вход: bi - указатель на блок заголовка
//      size - sizeof структуры
// выход: true - всё нормально
//      false - размер не совпадает.
bool CMSFManager::CheckBlockSize(MSF_BLOCK_INFO *pBi, DWORD size) const
{
	if (static_cast<uint32_t>(pBi->header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK)
	{
		if (pBi->header.flags & MSF_EX_FLAG_COMPRESSED)
		{
			// если данные запакованы, то мы тут не можем ничего поделать
			// потому, что передаём сюда размер распакованных данных и
			// ничего не знаем ни о том, что они запакованы ни об их запакованном размере
			return true;
		}

		return (pBi->header.hdr.length == sizeof(MSF_BLOCK_HEADEREX) + size);
	}

	return (pBi->header.hdr.length == sizeof(MSF_BLOCK_HEADER) + size);
}

bool CMSFManager::SetBlockTape(uint8_t *pPackedWave, DWORD nBitsLength, DWORD nPackLength) const
{
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(IDS_BK_ERROR_BLKTAPE));
	MSF_BLOCK_INFO bi
	{
		0, // offset
		{
			MSF_BLOCKTYPE::WAVE, // type
			sizeof(MSF_BLOCK_HEADER) + sizeof(uint32_t) + nPackLength // length
		}
	};

	// в целях совместимости будем сохранять tap только в старом формате

	if (SetBlockHeader(&bi)) // запишем заголовок
	{
		if (SetBlockData(reinterpret_cast<uint8_t *>(&nBitsLength), sizeof(uint32_t))) // запишем распакованный размер
		{
			if (SetBlockData(reinterpret_cast<uint8_t *>(pPackedWave), nPackLength)) // запишем сами данные
			{
				bRet = true;
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}

bool CMSFManager::GetBlockTape(uint8_t *pPackedWave, DWORD *pnBitsLength, DWORD *pnPackLength) const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(IDS_BK_ERROR_BLKTAPE));

	if (FindBlock(MSF_BLOCKTYPE::WAVE, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi)) // читаем заголовок
		{
			DWORD nPackLength = bi.header.hdr.length - sizeof(MSF_BLOCK_HEADER) - sizeof(uint32_t);

			if (GetBlockData(reinterpret_cast<uint8_t *>(pnBitsLength), sizeof(uint32_t))) // читаем распакованный размер
			{
				if (!pPackedWave) // если указатель на массив не указан
				{
					*pnPackLength = nPackLength; // возвращаем размер упакованного массива
					return true;
				}

				// иначе - читаем данные
				if (GetBlockData(reinterpret_cast<uint8_t *>(pPackedWave), nPackLength))
				{
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, CString(MAKEINTRESOURCE(IDS_BK_ERROR_BLKTAPE)));
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockPlatformData(MSF_PLATFORM_DATA *pPD) const
{
	ASSERT(pPD);    // по алгоритму, pPD никогда не должен быть null
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::PLATFORM)]));
	MSF_BLOCK_INFO bi
	{
		0, // offset
		{
			MSF_BLOCKTYPE::PLATFORM,    // type
			sizeof(MSF_BLOCK_HEADER) // размер заголовка
			+ sizeof(uint32_t) * 2 // размер двух первых двордов MSF_PLATFORM_DATA
			+ sizeof(MSF_DEVICE_ID) *pPD->nDevNum  // размер MSF_DEVICE_ID по количеству устройств
		}
	};

	if (SetBlockHeader(&bi))
	{
		if (SetBlockData(reinterpret_cast<uint8_t *>(pPD), sizeof(uint32_t) * 2)
		        && SetBlockData(reinterpret_cast<uint8_t *>(pPD->pDevices.data()), sizeof(MSF_DEVICE_ID) * pPD->nDevNum))
		{
			bRet = true;
		}
		else
		{
			str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}

bool CMSFManager::GetBlockPlatformData(MSF_PLATFORM_DATA *pPD) const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::PLATFORM)]));

	if (FindBlock(MSF_BLOCKTYPE::PLATFORM, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi))
		{
			if (GetBlockData(reinterpret_cast<uint8_t *>(pPD), sizeof(uint32_t) * 2))
			{
				if (pPD->nDevNum)
				{
					pPD->pDevices.resize(pPD->nDevNum);

					if (pPD->pDevices.data())
					{
						if (GetBlockData(reinterpret_cast<uint8_t *>(pPD->pDevices.data()), sizeof(MSF_DEVICE_ID) * pPD->nDevNum))
						{
							bRet = true;
						}
					}
					else
					{
						g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
					}
				}
				else
				{
					pPD->pDevices.clear();
					bRet = true;
				}
			}

			if (!bRet)
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockPreview(HBITMAP hScreenshot) const
{
	DIBSECTION ds{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::PREVIEW)]));

	if (::GetObject(hScreenshot, sizeof(ds), &ds))
	{
		ASSERT(ds.dsBm.bmBits);
		MSF_BLOCK_INFO bi
		{
			0,
			{
				MSF_BLOCKTYPE::PREVIEW,
				sizeof(MSF_BLOCK_HEADER) + sizeof(ds.dsBmih) + ds.dsBmih.biSizeImage,
			}
		};

		if (SetBlockHeader(&bi))
		{
			if (SetBlockData(reinterpret_cast<uint8_t *>(&ds.dsBmih), sizeof(ds.dsBmih)))
			{
				if (SetBlockData(reinterpret_cast<uint8_t *>(ds.dsBm.bmBits), ds.dsBmih.biSizeImage))
				{
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_MSFPREVIEWBLK_OBJ);
	}

	return bRet;
}

bool CMSFManager::SetBlockPreviewEx(HBITMAP hScreenshot, const bool bCompressed) const
{
	DIBSECTION ds{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::PREVIEW)]));

	if (::GetObject(hScreenshot, sizeof(ds), &ds))
	{
		ASSERT(ds.dsBm.bmBits);
		MSF_BLOCK_INFO bi
		{
			0,
			{
				MSF_BLOCKTYPE::PREVIEWEX,
				sizeof(MSF_BLOCK_HEADEREX) + sizeof(ds.dsBmih),
				uint32_t(-1), // id
				bCompressed ? MSF_EX_FLAG_COMPRESSED : 0, // flags
				ds.dsBmih.biSizeImage // распакованный размер
			}
		};
		uint32_t nStoredSize = ds.dsBmih.biSizeImage;
		auto pStoredData = reinterpret_cast<uint8_t *>(ds.dsBm.bmBits);
		std::vector<uint16_t> pPackLZW;

		if (bCompressed)
		{
			nStoredSize = static_cast<uint32_t>(PackLZW_Fast(reinterpret_cast<uint8_t *>(ds.dsBm.bmBits), nStoredSize, pPackLZW)) * 2; // нужно сделать размер в байтах
			pStoredData = reinterpret_cast<uint8_t *>(pPackLZW.data());
		}

		// корректируем размер в заголовке
		bi.header.hdr.length += nStoredSize;

		if (SetBlockHeader(&bi))
		{
			if (SetBlockData(reinterpret_cast<uint8_t *>(&ds.dsBmih), sizeof(ds.dsBmih)))
			{
				if (SetBlockData(pStoredData, nStoredSize))
				{
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_MSFPREVIEWBLK_OBJ);
	}

	return bRet;
}

bool CMSFManager::GetBlockPreview(HBITMAP *hScreenshot, uint8_t **pBits) const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::PREVIEW)]));

	if (FindBlock(MSF_BLOCKTYPE::PREVIEW, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi))
		{
			BITMAPINFOHEADER bmih{};

			if (GetBlockData(reinterpret_cast<uint8_t *>(&bmih), sizeof(BITMAPINFOHEADER)))
			{
				*pBits = nullptr;

				if ((*hScreenshot = ::CreateDIBSection(nullptr, (BITMAPINFO *)&bmih, DIB_RGB_COLORS, (void **)pBits, nullptr, 0)))
				{
					if (GetBlockData(*pBits, bmih.biSizeImage))
					{
						bRet = true;
					}
					else
					{
						str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
						g_BKMsgBox.Show(str, MB_OK);
					}
				}
				else
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_MSFPREVIEWBLK_DIB, MB_OK);
				}
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else if (FindBlock(MSF_BLOCKTYPE::PREVIEWEX, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi))
		{
			BITMAPINFOHEADER bmih{};

			if (GetBlockData(reinterpret_cast<uint8_t *>(&bmih), sizeof(BITMAPINFOHEADER)))
			{
				*pBits = nullptr;

				if ((*hScreenshot = ::CreateDIBSection(nullptr, (BITMAPINFO *)&bmih, DIB_RGB_COLORS, (void **)pBits, nullptr, 0)))
				{
					if (bi.header.flags & MSF_EX_FLAG_COMPRESSED)
					{
						uint32_t nPackBitsLength = bi.header.unpSize;
						uint32_t nPackLZWLength = bi.header.hdr.length - sizeof(BITMAPINFOHEADER) - sizeof(MSF_BLOCK_HEADEREX);
						auto pPackLZW = std::vector<uint16_t>(nPackLZWLength / 2); // не забываем, nPackLZWLength это размер в байтах, а нужно количество слов

						if (GetBlockData(reinterpret_cast<uint8_t *>(pPackLZW.data()), nPackLZWLength))
						{
							UnpackLZW_Fast(*pBits, nPackBitsLength, pPackLZW);
							bRet = true;
						}
						else
						{
							str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
							g_BKMsgBox.Show(str, MB_OK);
						}
					}
					else
					{
						if (GetBlockData(*pBits, bmih.biSizeImage))
						{
							bRet = true;
						}
						else
						{
							str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
							g_BKMsgBox.Show(str, MB_OK);
						}
					}
				}
				else
				{
					g_BKMsgBox.Show(IDS_BK_ERROR_MSFPREVIEWBLK_DIB, MB_OK);
				}
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockConfig() const
{
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::CONFIG)]));
	MSF_BLOCK_INFO bi{ 0,
		{
			MSF_BLOCKTYPE::CONFIG,
			sizeof(MSF_BLOCK_HEADER)
		}  // + длина блока. длина переменная
	};
	UINT nSize = _MAX_PATH * 1000;
	auto pBlock = std::vector<uint8_t>(nSize); // размер будет достаточен наверняка, 1000 строк длиной 256 байтов

	if (pBlock.data())
	{
		ZeroMemory(pBlock.data(), nSize);
		ULONGLONG len = g_Config.SaveConfig_ToMemory(pBlock.data(), nSize);
		bi.header.hdr.length += uint32_t(len);

		if (SetBlockHeader(&bi))
		{
			if (SetBlockData(pBlock.data(), uint32_t(len)))
			{
				bRet = true;
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return bRet;
}

bool CMSFManager::SetBlockConfigEx(const bool bCompressed) const
{
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::CONFIG)]));
	MSF_BLOCK_INFO bi{ 0,
		{
			MSF_BLOCKTYPE::CONFIGEX,
			sizeof(MSF_BLOCK_HEADEREX), // + длина блока. длина переменная
			uint32_t(-1), // id
			bCompressed ? MSF_EX_FLAG_COMPRESSED : 0, // flags
			0 // распакованный размер
		}
	};
	UINT nSize = _MAX_PATH * 1000;
	auto pBlock = std::vector<uint8_t>(nSize); // размер будет достаточен наверняка, 1000 строк длиной 256 байтов

	if (pBlock.data())
	{
		ZeroMemory(pBlock.data(), nSize);
		ULONGLONG len = g_Config.SaveConfig_ToMemory(pBlock.data(), nSize);
		pBlock.resize(len);
		bi.header.unpSize = uint32_t(len); //корректируем распакованный размер
		auto nStoredSize = uint32_t(len);
		auto pStoredData = reinterpret_cast<uint8_t *>(pBlock.data());
		std::vector<uint16_t> pPackLZW;

		if (bCompressed)
		{
			nStoredSize = static_cast<uint32_t>(PackLZW_Fast(pBlock.data(), nStoredSize, pPackLZW)) * 2; // нужно сделать размер в байтах
			pStoredData = reinterpret_cast<uint8_t *>(pPackLZW.data());
		}

		// корректируем размер в заголовке
		bi.header.hdr.length += nStoredSize;

		if (SetBlockHeader(&bi))
		{
			if (SetBlockData(pStoredData, nStoredSize))
			{
				bRet = true;
			}
			else
			{
				str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return bRet;
}

bool CMSFManager::GetBlockConfig() const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::CONFIG)]));

	if (FindBlock(MSF_BLOCKTYPE::CONFIG, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi))
		{
			DWORD nBlockLength = bi.header.hdr.length - sizeof(MSF_BLOCK_HEADER);
			auto pBlock = std::vector<uint8_t>(nBlockLength);

			if (pBlock.data())
			{
				if (GetBlockData(pBlock.data(), nBlockLength))
				{
					g_Config.LoadConfig_FromMemory(pBlock.data(), nBlockLength);
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
			else
			{
				g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else if (FindBlock(MSF_BLOCKTYPE::CONFIGEX, uint32_t(-1), &bi))
	{
		if (GetBlockHeader(&bi))
		{
			DWORD nBlockLength = bi.header.hdr.length - sizeof(MSF_BLOCK_HEADEREX);

			if (bi.header.flags & MSF_EX_FLAG_COMPRESSED)
			{
				auto pPackLZW = std::vector<uint16_t>(nBlockLength / 2); // не забываем, nBlockLength это размер в байтах, а нужно количество слов
				uint32_t nPackBitsLength = bi.header.unpSize;

				if (GetBlockData(reinterpret_cast<uint8_t *>(pPackLZW.data()), nBlockLength))
				{
					auto pPackBits = std::vector<uint8_t>(nPackBitsLength);
					UnpackLZW_Fast(pPackBits.data(), nPackBitsLength, pPackLZW);
					g_Config.LoadConfig_FromMemory(pPackBits.data(), nPackBitsLength);
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
			else
			{
				auto pBlock = std::vector<uint8_t>(nBlockLength);

				if (GetBlockData(pBlock.data(), nBlockLength))
				{
					g_Config.LoadConfig_FromMemory(pBlock.data(), nBlockLength);
					bRet = true;
				}
				else
				{
					str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
					g_BKMsgBox.Show(str, MB_OK);
				}
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockFrameData(MSF_FRAMEDATA *FDBlock) const
{
	return SetBlock(MSF_BLOCKTYPE::FRAMEDATA, uint32_t(-1), reinterpret_cast<uint8_t *>(FDBlock), sizeof(MSF_FRAMEDATA), false);
}

bool CMSFManager::GetBlockFrameData(MSF_FRAMEDATA *FDBlock) const
{
	if (m_header.version < MSF_VERSION_CURRENT)
	{
		return false;
	}

	return GetBlock(MSF_BLOCKTYPE::FRAMEDATA, uint32_t(-1), reinterpret_cast<uint8_t *>(FDBlock), sizeof(MSF_FRAMEDATA));
}


bool CMSFManager::SetBlockCPURegisters(MSF_CPU_REGISTERS *pCPURegisters) const
{
	return SetBlock(MSF_BLOCKTYPE::CPU_REGISTERS, uint32_t(-1), reinterpret_cast<uint8_t *>(pCPURegisters), sizeof(MSF_CPU_REGISTERS), false);
}

bool CMSFManager::GetBlockCPURegisters(MSF_CPU_REGISTERS *pCPURegisters) const
{
	return GetBlock(MSF_BLOCKTYPE::CPU_REGISTERS, uint32_t(-1), reinterpret_cast<uint8_t *>(pCPURegisters), sizeof(MSF_CPU_REGISTERS));
}


bool CMSFManager::SetBlockMemMap(uint32_t id, BKMEMBank_t *MBType, UINT nMemMapSize, uint32_t nROMPresent) const
{
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::MEMMAP)]));
	MSF_BLOCK_INFO bi{ 0,
		{
			MSF_BLOCKTYPE::MEMMAPEX,
			sizeof(MSF_BLOCK_HEADEREX) + sizeof(BKMEMBank_t) *nMemMapSize + sizeof(nROMPresent),
			id,
			0, 0
		}
	};

	if (SetBlockHeader(&bi))
	{
		if (SetBlockData(reinterpret_cast<uint8_t *>(MBType), sizeof(BKMEMBank_t) * nMemMapSize)
		        && SetBlockData(reinterpret_cast<uint8_t *>(&nROMPresent), sizeof(nROMPresent)))
		{
			bRet = true;
		}
		else
		{
			str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}

bool CMSFManager::GetBlockMemMap(uint32_t id, BKMEMBank_t *MBType, UINT nMemMapSize, uint32_t &nROMPresent) const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(MSF_BLOCKTYPE::MEMMAP)]));

	if (FindBlock(MSF_BLOCKTYPE::MEMMAPEX, id, &bi))
	{
		if (GetBlockHeader(&bi))
		{
			if (CheckBlockSize(&bi, sizeof(BKMEMBank_t) * nMemMapSize) + sizeof(nROMPresent))
			{
				bRet = GetBlockData(reinterpret_cast<uint8_t *>(MBType), sizeof(BKMEMBank_t) * nMemMapSize);
				uint32_t data = 0;
				bRet = bRet && GetBlockData(reinterpret_cast<uint8_t *>(&data), sizeof(nROMPresent));
				nROMPresent = data;
			}

			if (!bRet)
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		// в старых версиях этого блока не было,
		// поэтому старые версии будут не совместимы с новой. и ничего тут не поделать
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


bool CMSFManager::SetBlockBaseMemory(uint32_t id, const uint8_t *pMemory, UINT nSize) const
{
	return SetBlock(MSF_BLOCKTYPE::BASEMEMORYEX, id, pMemory, nSize, false);
}

bool CMSFManager::SetBlockBaseMemoryEx(uint32_t id, const uint8_t *pMemory, UINT nSize, const bool bCompressed) const
{
	return SetBlock(MSF_BLOCKTYPE::BASEMEMORYEX, id, pMemory, nSize, bCompressed);
}

bool CMSFManager::GetBlockBaseMemory(uint32_t id, const uint8_t *pMemory, UINT nSize) const
{
	return GetBlock(MSF_BLOCKTYPE::BASEMEMORYEX, id, const_cast<uint8_t*>(pMemory), nSize);
}


bool CMSFManager::SetBlockPortRegs(uint32_t id, MSF_PORT_REGS *pPortRegs) const
{
	return SetBlock(MSF_BLOCKTYPE::PORT_REGSEX, id, reinterpret_cast<uint8_t *>(pPortRegs), sizeof(MSF_PORT_REGS), false);
}

bool CMSFManager::GetBlockPortRegs(uint32_t id, MSF_PORT_REGS *pPortRegs) const
{
	if (m_header.version < MSF_VERSION_MINIMAL)
	{
		return false;
	}

	return GetBlock(MSF_BLOCKTYPE::PORT_REGSEX, id, reinterpret_cast<uint8_t *>(pPortRegs), sizeof(MSF_PORT_REGS));
}


bool CMSFManager::SetBlockCustomData(MSF_BLOCKTYPE type, uint32_t id, uint8_t *pData, UINT nSize) const
{
	return SetBlock(type, id, pData, nSize, false);
}

bool CMSFManager::GetBlockCustomData(MSF_BLOCKTYPE type, uint32_t id, uint8_t *pData, UINT nSize) const
{
	return GetBlock(type, id, pData, nSize);
}

const UINT CMSFManager::m_arBlkStrID[] =
{
	IDS_BK_ERROR_BLKBASEMEM,
	IDS_BK_ERROR_BLKCPUREGS,
	IDS_BK_ERROR_BLKPREVIEW,
	IDS_BK_ERROR_BLKEXT16MEM,
	IDS_BK_ERROR_BLKEXT32MEM,
	IDS_BK_ERROR_BLKPLATFORMDATA,
	IDS_BK_ERROR_BLKSYSREGS,
	IDS_BK_ERROR_BLKMEMMAPMGR,
	IDS_BK_ERROR_BLKBASEMEM11,
	IDS_BK_ERROR_BLKSMK512MEM,
	IDS_BK_ERROR_BLKCONFIG,
	IDS_BK_ERROR_BLKFRAMEDATA,
	IDS_BK_ERROR_BLKCUSTOM,
};

// TODO: добавить компрессию
bool CMSFManager::SetBlock(MSF_BLOCKTYPE bt, uint32_t id, const uint8_t *pData, UINT nSize, const bool bCompressed) const
{
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(bt) & 0xff]));
	MSF_BLOCK_INFO bi{ 0,
		{
			bt,
			static_cast<uint32_t>(bt) &MSF_BLOCKTYPE_EXT_MASK ? sizeof(MSF_BLOCK_HEADEREX) : sizeof(MSF_BLOCK_HEADER),
			id,
			bCompressed ? MSF_EX_FLAG_COMPRESSED : 0,
			nSize
		}
	};
	uint32_t nStoredSize = nSize;
	const uint8_t *pStoredData = pData;
	std::vector<uint16_t> pPackLZW;

	if (bCompressed)
	{
		nStoredSize = static_cast<uint32_t>(PackLZW_Fast(pData, nSize, pPackLZW)) * 2; // нужно сделать размер в байтах
		pStoredData = reinterpret_cast<uint8_t *>(pPackLZW.data());
	}

	bi.header.hdr.length += nStoredSize;

	if (SetBlockHeader(&bi))
	{
		if (SetBlockData(pStoredData, nStoredSize))
		{
			bRet = true;
		}
		else
		{
			str.Format(IDS_BK_ERROR_DTABLKWRERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_HDRBLKWRERR, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}

bool CMSFManager::GetBlock(MSF_BLOCKTYPE bt, uint32_t id, uint8_t *pData, UINT nSize) const
{
	MSF_BLOCK_INFO bi{};
	CString str;
	bool bRet = false;
	CString strMsg = CString(MAKEINTRESOURCE(m_arBlkStrID[static_cast<UINT>(bt) & 0xff]));

	if (FindBlock(bt, id, &bi))
	{
		if (GetBlockHeader(&bi))
		{
			if (CheckBlockSize(&bi, nSize))
			{
				if (static_cast<uint32_t>(bi.header.hdr.type) & MSF_BLOCKTYPE_EXT_MASK && bi.header.flags & MSF_EX_FLAG_COMPRESSED)
				{
					uint32_t nPackLZWLength = bi.header.hdr.length - sizeof(MSF_BLOCK_HEADEREX);
					auto pPackLZW = std::vector<uint16_t>(nPackLZWLength / 2); // не забываем, nPackLZWLength это размер в байтах, а нужно количество слов

					if (GetBlockData(reinterpret_cast<uint8_t *>(pPackLZW.data()), nPackLZWLength))
					{
						UnpackLZW_Fast(pData, nSize, pPackLZW);
						bRet = true;
					}
				}
				else
				{
					bRet = GetBlockData(pData, nSize);
				}
			}

			if (!bRet)
			{
				str.Format(IDS_BK_ERROR_DTABLKRDERR, strMsg);
				g_BKMsgBox.Show(str, MB_OK);
			}
		}
		else
		{
			str.Format(IDS_BK_ERROR_HDRBLKRDERR, strMsg);
			g_BKMsgBox.Show(str, MB_OK);
		}
	}
	else
	{
		str.Format(IDS_BK_ERROR_BLKNOTFOUND, strMsg);
		g_BKMsgBox.Show(str, MB_OK);
	}

	return bRet;
}


// в принципе, работает, но только вот заметно медленно.


constexpr auto MAX_TABLE_SIZE = 65000;
constexpr uint16_t BAD_CODE = 65535;
constexpr auto PREFIX = 256;
constexpr auto CODE = 257;

void CMSFManager::InitTables() const
{
	m_vScanTable.clear();
	m_vScanTable.assign(MAX_TABLE_SIZE, std::vector<uint16_t>(CODE + 1, BAD_CODE));

	for (uint16_t y = 0; y < 256; ++y)
	{
		m_vScanTable[y][CODE] = y;
	}

	m_nScanTableSize = CODE;
}


void CMSFManager::ClearTables() const
{
	for (auto &y : m_vScanTable)
	{
		y.clear();
	}

	m_vScanTable.clear();
	m_nScanTableSize = 0;
}

uint16_t CMSFManager::TableLookup_Fast(const uint16_t prefix, const uint8_t v) const
{
	if (prefix == BAD_CODE)
	{
		return v;
	}

	return m_vScanTable.at(prefix).at(v);
}


void CMSFManager::AddWord_Fast(const uint16_t prefix, const uint8_t v) const
{
	// если размер таблицы превысил все допустимые рамки
	if (m_nScanTableSize >= MAX_TABLE_SIZE)
	{
		// обнулим его, и начнём заполнять сначала
		InitTables();
		return;
	}

	m_vScanTable[prefix][v] = m_nScanTableSize;
	m_vScanTable[m_nScanTableSize][PREFIX] = prefix;
	m_vScanTable[m_nScanTableSize][CODE] = v;
	m_nScanTableSize++;
}

/*
    Упаковка.
    Вход: vPackBits - упаковываемый массив
          vPackLZW - запакованный массив
    Выход: размер запакованных данных в словах.
*/
size_t CMSFManager::PackLZW_Fast(const uint8_t *pPackBits, const size_t nPackBitsLength, std::vector<uint16_t> &vPackLZW) const
{
	/*
	vPackBits[0] = 'A';
	vPackBits[1] = 'B';
	vPackBits[2] = 'C';
	vPackBits[3] = 'A';
	vPackBits[4] = 'B';
	vPackBits[5] = 'C';
	vPackBits[6] = 'A';
	vPackBits[7] = 'B';
	vPackBits[8] = 'C';
	vPackBits[9] = 'A';
	vPackBits[10] = 'B';
	vPackBits[11] = 'C';
	vPackBits[12] = 'A';
	vPackBits[13] = 'B';
	vPackBits[14] = 'C';
	vPackBits[15] = 'A';
	vPackBits[16] = 'B';
	vPackBits[17] = 'C';
	vPackBits[18] = 'A';
	vPackBits[19] = 'B';
	vPackBits[20] = 'C';
	vPackBits[21] = 'A';
	vPackBits[22] = 'B';
	vPackBits[23] = 'C';
	vPackBits[24] = 'A';
	vPackBits[25] = 'B';
	vPackBits[26] = 'C';
	vPackBits[27] = 'A';
	vPackBits[28] = 'B';
	vPackBits[29] = 'C';
	*/
	vPackLZW.clear();
	// Инициализируем таблицы
	InitTables();
	uint16_t last_code = BAD_CODE;
	size_t read_pos;

	for (read_pos = 0; read_pos < nPackBitsLength; ++read_pos)
	{
		auto v = pPackBits[read_pos];
		uint16_t code = TableLookup_Fast(last_code, v);  // Берём текущую последовательность

		if (code == BAD_CODE || read_pos == (nPackBitsLength - 1))
		{
			// Если нет в таблице, или уже конец
			vPackLZW.push_back(last_code);  // Упаковка: последовательность - 1 байт
			AddWord_Fast(last_code, v);  // Добавление: последовательность в таблицу
			last_code = v;
		}
		else
		{
			last_code = code;
		}
	}

	vPackLZW.push_back(pPackBits[read_pos - 1]); // Пакуем последний байт как есть
	// очищаем таблицы
	ClearTables();
	return vPackLZW.size(); // Возвращаем длину сжатого массива
}


// алгоритм без рекурсии

bool CMSFManager::UnpackWord(uint8_t *pPackBits, size_t &p, uint16_t code) const
{
	std::vector<uint8_t> buf;

	while (true)
	{
		if (code == BAD_CODE)
		{
			return false;
		}

		if (code < 256)
		{
			pPackBits[p++] = LOBYTE(code);

			while (!buf.empty())
			{
				pPackBits[p++] = buf.back();
				buf.pop_back();
			}

			return true;
		}

		auto &tbl = m_vScanTable.at(code);
		code = tbl.at(PREFIX);
		buf.push_back(LOBYTE(tbl.at(CODE)));
	}

	return false;
}

/*
* // !!! Вот эта функция вызывает переполнение стека из-за рекурсии

bool CMSFManager::UnpackWord(uint8_t *pPackBits, size_t &p, const uint16_t code) const
{
    if (code == BAD_CODE)
    {
        return false;
    }

    if (code < 256)
    {
        pPackBits[p++] = LOBYTE(code);
    }
    else
    {
        auto &tbl = m_vScanTable.at(code);

        if (!UnpackWord(pPackBits, p, tbl.at(PREFIX)))
        {
            return false;
        }

        pPackBits[p++] = LOBYTE(tbl.at(CODE));
    }

    return true;
}
*/


/*
    Распаковка.
    Вход: vPackBits - куда распаковывать данные
          vPackLZW - запакованный массив
*/
void CMSFManager::UnpackLZW_Fast(uint8_t *pPackBits, const size_t nPackBitsLength, std::vector<uint16_t> const &vPackLZW) const
{
	/*
	vPackLZW[0] = 'A';
	vPackLZW[1] = 'B';
	vPackLZW[2] = 'C';
	vPackLZW[3] = 257;
	vPackLZW[4] = 259;
	vPackLZW[5] = 258;
	vPackLZW[6] = 260;
	vPackLZW[7] = 263;
	vPackLZW[8] = 262;
	vPackLZW[9] = 265;
	vPackLZW[10] = 261;
	vPackLZW[11] = 267;
	*/
	// Инициализируем таблицы
	InitTables();
	// Очищаем будущий распакованный массив
	memset(pPackBits, 0, nPackBitsLength);
	size_t unpack_pos = 0;
	size_t ptr = 0; // текущий указатель для распаковки
	uint16_t last_code = BAD_CODE;

	for (const auto &lzv : vPackLZW)
	{
		if (lzv < 256) // Если текущий код < 256, распакуем его как есть
		{
			pPackBits[ptr++] = uint8_t(lzv);
		}
		else
		{
			// Иначе создаём таблицу от текущей позиции до позиции этого кода
			while (unpack_pos < ptr)
			{
				auto v = pPackBits[unpack_pos];
				uint16_t code = TableLookup_Fast(last_code, v);

				if (code == BAD_CODE)
				{
					// Если нет текущей последовательности в таблице, добавим её
					AddWord_Fast(last_code, v);
					last_code = v;
				}
				else
				{
					last_code = code;
				}

				unpack_pos++;
			}

			if (!UnpackWord(pPackBits, ptr, lzv))  // Пробуем распаковать текущий код
			{
				// Если нет в таблице (случай XиX)
				size_t first_pos = ptr;
				UnpackWord(pPackBits, ptr, last_code);  // Распаковываем последний известный код
				pPackBits[ptr++] = LOBYTE(TableLookup_Fast(BAD_CODE, pPackBits[first_pos]));  // Распаковываем последний известный код первого байта
				AddWord_Fast(last_code, pPackBits[unpack_pos]);  // Добавление: Xпоследовательность + X в таблицу
				last_code = BAD_CODE;
			}
		}
	}

	// очищаем таблицы
	ClearTables();
}


