#include "pch.h"

#include "BKHDDTool.h"
#include "HDIStuff.h"

extern USector g_sector;
extern ProrgamOptions g_options;
extern std::array<uint16_t, SECTOR_SIZEW> g_MBRAltPro;

// конвертируем образ альтпро в самару
// Вход: f - входной файл образа
//  imgf - его формат и характеристики.
bool BKHDDTool::ConvertAltPro2Samara(FILE *f, IMGFormat *InImgF)
{
	bool bRet = false;
	USector buf{}, block1{};
	block1.b.fill(0);

	// прочитаем сектор 8, нам нужна таблица разделов.
	if ((fseek(f, (AHDD_PT_SEC + (InImgF->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
	        && (fread(g_sector.b.data(), 1, SECTOR_SIZEB, f) == SECTOR_SIZEB))
	{
		int nErrors = 0;
		HDIStuff::InverseSector(g_sector);
		//////////////////////////////////
		IMGFormat OutImgF = *InImgF;
		OutImgF.bSamara = true;
		OutImgF.nCyl = 0;
		OutImgF.nHead = 0;
		OutImgF.nSector = 0;
		OutImgF.nLogDiskNum = 0;
		uint16_t Outboot = g_sector.b[AHDD_UNI_B];
		uint32_t Outlba = 3;
		fs::path title = g_options.strImgName.stem();
		title += _T(".samara");
		title += g_options.strImgName.extension();
		fs::path strOutImgName = g_options.strImgName;
		strOutImgName.replace_filename(title);
		FILE *OutF = _tfopen(strOutImgName._tstring().c_str(), _T("wb"));

		if (OutF == nullptr)
		{
			std::_tcout << _T("Не удалось создать файл ") << strOutImgName << std::endl;
			return false;
		}

		if (OutImgF.bHDI)
		{
			// создаём заголовочный сектор HDI.
			HDIStuff::CreateHDISector(&OutImgF, &buf, HDIStuff::GenerateSerialNumber(), g_HDIModelName);

			if (SECTOR_SIZEB != fwrite(buf.b.data(), 1, SECTOR_SIZEB, OutF))
			{
				std::_tcout << _T("Не удалось записать HDI сектор в файл ") << strOutImgName << std::endl;
				return false;
			}
		}

		WriteEmptySectors(OutF, InImgF, Outlba); // сперва запишем 3 пустых сектора.
		//////////////////////////////////

		for (int nld = 0; nld < InImgF->nLogDiskNum; ++nld)
		{
			uint16_t nCyl = g_sector.w[AHDD_PART_W - nld * 2]; // первое слово из таблицы разделов (старший бит - флаг защиты от записи)
			bool bProtected = false;

			if (static_cast<short>(nCyl) < 0)
			{
				bProtected = true;
				nCyl = ~nCyl;
			}

			const uint16_t nHead = nCyl & 0xF; // номер головки из таблицы разделов
			nCyl >>= 4; // номер дорожки из таблицы разделов
			const uint32_t lba = (nCyl * InImgF->H + nHead) * InImgF->S; // рассчитываем начало раздела в блоках
			const uint32_t nLen = g_sector.w[AHDD_PART_W - 1 - nld * 2]; // размер логического диска в блоках
			_tprintf(_T("part #%d:: %c: cyl = %d\thd = %d\tsec = 1\tlba = %ud\tlen = %d"), nld, _T('C') + nld, nCyl, nHead, lba, nLen);

			if (bProtected)
			{
				std::_tcout << _T(" [WP]\n");
			}
			else
			{
				std::_tcout << _T(" [ ]\n");
			}

			if (fseek(f, (lba + (InImgF->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
			{
				//////////////////////////////////
				// сперва надо создать начальный сектор
				uint16_t OutLen = nLen & 0xffff;
				buf.b.fill(0);
				buf.w[SHDD_NLD_W] = OutImgF.nLogDiskNum + 2;      // номер ld
				buf.w[SHDD_LD_LEN_W] = OutLen;  // длина ld
				buf.w[SHDD_LD_FLAGS_W] = bProtected ? ALD_ATTR::WP : ALD_ATTR::NONE; // флаги - все нули
				buf.w[SHDD_ADR_BOOT_W] = 02000; // адрес загрузки загрузчика :)
				buf.w[SHHD_ADR_PAR_W] = 01000;  // адрес блока параметров для загрузчика
				buf.w[SHDD_PAGE_W] = 014400;    // состояние регистра страниц
				block1.w[SHDD_PART_W + OutImgF.nLogDiskNum * 2] = Outlba & 0xffff;
				block1.w[SHDD_PART_W + 1 + OutImgF.nLogDiskNum * 2] = (Outlba >> 16) & 0xffff;
				HDIStuff::InverseSector(buf);

				if ((fwrite(buf.b.data(), 1, SECTOR_SIZEB, OutF)) != SECTOR_SIZEB)
				{
					std::_tcout << _T("Ошибка записи в файл ") << strOutImgName << std::endl;
					return false;
				}

				//////////////////////////////////

				if (!(bRet = ConvertLogDiskImage(f, OutF, &OutImgF, OutLen)))
				{
					std::_tcout << _T("\nОшибка извлечения раздела ") << nld << std::endl;
					nErrors++;
				}

				//////////////////////////////////
				Outlba += OutLen + 1;
				OutImgF.nLogDiskNum++;
				//////////////////////////////////
			}
			else
			{
				std::_tcout << _T("\nОшибка перемещения к разделу ") << nld << std::endl;
				nErrors++;
			}
		}

		//////////////////////////////////
		if (bRet)
		{
			WriteEmptyTail(OutF, &OutImgF);
			block1.w[SHDD_BOOT_W] = Outboot; // boot
			block1.w[SHDD_CYLVOL_W] = OutImgF.H * OutImgF.S;
			block1.b[SHDD_HEAD_B] = (OutImgF.H & 0xff) - 1;
			block1.b[SHDD_SEC_B] = (OutImgF.S & 0xff);
			fseek(OutF, (SHDD_PT_SEC + (OutImgF.bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET);
			HDIStuff::InverseSector(block1);

			if ((fwrite(block1.b.data(), 1, SECTOR_SIZEB, OutF)) != SECTOR_SIZEB)
			{
				std::_tcout << _T("Ошибка записи в файл ") << strOutImgName << std::endl;
				bRet = false;
			}
		}

		//////////////////////////////////
		std::_tcout << _T("\nУспешно сконвертировано ") << (InImgF->nLogDiskNum - nErrors) << _T(" разделов\n");
		fclose(OutF);

		if (nErrors)
		{
			std::_tcout << _T("С ошибками - ") << nErrors << _T(" разделов\n");
		}
	}
	else
	{
		std::_tcout << _T("Ошибка чтения таблицы разделов формата 'АльтПро'\n");
	}

	return bRet;
}


// конвертируем образ самара в альтпро
// Вход: f - входной файл образа
//  imgf - его формат и характеристики.
bool BKHDDTool::ConvertSamara2Altpro(FILE *f, const IMGFormat *InImgF)
{
	bool bRet = false;
	USector buf{}, block1{};
	block1.b.fill(0);

	// прочитаем сектор 2, нам нужна таблица разделов.
	if ((fseek(f, (SHDD_PT_SEC + (InImgF->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
	        && (fread(g_sector.b.data(), 1, SECTOR_SIZEB, f) == SECTOR_SIZEB))
	{
		int nErrors = 0;
		HDIStuff::InverseSector(g_sector);
		//////////////////////////////////
		IMGFormat OutImgF = *InImgF;
		OutImgF.bSamara = false;
		OutImgF.nCyl = 0;
		OutImgF.nHead = 0;
		OutImgF.nSector = 0;
		OutImgF.nLogDiskNum = 0;
		// в block1 будем формировать таблицу разделов.
		block1.b.fill(0);
		uint16_t boot = g_sector.w[SHDD_BOOT_W];
		fs::path title = g_options.strImgName.stem();
		title += _T(".altpro");
		title += g_options.strImgName.extension();
		fs::path strOutImgName = g_options.strImgName;
		strOutImgName.replace_filename(title);
		FILE *OutF = _tfopen(strOutImgName._tstring().c_str(), _T("wb"));

		if (OutF == nullptr)
		{
			std::_tcout << _T("Не удалось создать файл ") << strOutImgName << std::endl;
			return false;
		}

		if (OutImgF.bHDI)
		{
			// создаём заголовочный сектор HDI.
			HDIStuff::CreateHDISector(&OutImgF, &buf, HDIStuff::GenerateSerialNumber(), g_HDIModelName);

			if (SECTOR_SIZEB != fwrite(buf.b.data(), 1, SECTOR_SIZEB, OutF))
			{
				std::_tcout << _T("Не удалось записать HDI сектор в файл ") << strOutImgName << std::endl;
				return false;
			}
		}

		WriteEmptySectors(OutF, &OutImgF, OutImgF.S); // запишем пустую нулевую дорожку
		//////////////////////////////////

		for (int nld = 0; nld < InImgF->nLogDiskNum; ++nld)
		{
			const uint32_t lba = ((uint32_t(g_sector.w[SHDD_PART_W + 1 + nld * 2]) << 16) | uint32_t(g_sector.w[SHDD_PART_W + nld * 2]));

			// перемещаемся к началу лог диска
			if (fseek(f, (lba + (InImgF->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
			{
				// читаем начальный блок
				if ((fread(buf.b.data(), 1, SECTOR_SIZEB, f)) == SECTOR_SIZEB)
				{
					HDIStuff::InverseSector(buf);
					const uint16_t nLen = buf.w[SHDD_LD_LEN_W];
					const uint8_t num = buf.b[SHDD_NLD_W];
					const uint8_t attr = buf.b[SHDD_LD_FLAGS_W * 2];
					_tprintf(_T("part #%d:: %c: lba = %ud\tlen = %d\t"), nld, _T('A') + num, lba, nLen);
					_tprintf(_T("ldr=%06o bpar=%06o pg=%06o ["), buf.w[SHDD_ADR_BOOT_W], buf.w[SHHD_ADR_PAR_W], buf.w[SHDD_PAGE_W]);

					if (attr & ALD_ATTR::BAD)
					{
						std::_tcout << _T("BAD ");
					}

					bool bProtect = false;

					if (attr & ALD_ATTR::WP)
					{
						std::_tcout << _T("WP ");
						bProtect = true;
					}

					if (attr & ALD_ATTR::RP)
					{
						std::_tcout << _T("RP ");
					}

					if (attr & ALD_ATTR::FWP)
					{
						std::_tcout << _T("FWP ");
						bProtect = true;
					}

					if (attr & ALD_ATTR::FRP)
					{
						std::_tcout << _T("FRP ");
					}

					std::_tcout << _T("]\n");

					//////////////////////////////////
					if (OutImgF.nCyl >= 04000)
					{
						std::_tcout << _T("Ошибка! Превышение допустимого номера цилиндра!\n");
						nErrors++;
						break;
					}

					const int beginLba = (OutImgF.nCyl * OutImgF.H + OutImgF.nHead) * OutImgF.S;
					uint16_t w1 = (OutImgF.nHead & 0xf) | (OutImgF.nCyl << 4);

					if (bProtect)
					{
						w1 = ~w1;
					}

					block1.w[AHDD_PART_W - OutImgF.nLogDiskNum * 2] = w1;

					//////////////////////////////////
					if (!(bRet = ConvertLogDiskImage(f, OutF, &OutImgF, nLen)))
					{
						std::_tcout << _T("\nОшибка извлечения раздела ") << nld << std::endl;
						nErrors++;
					}

					//////////////////////////////////
					bRet = WriteEmptySectors(OutF, &OutImgF);
					const int endLba = (OutImgF.nCyl * OutImgF.H + OutImgF.nHead) * OutImgF.S;
					int len = endLba - beginLba;
					// размер высчитывается вместе с пустым пространством, выравнивающим разделы по началу дорожки
					// хотя никто не мешает задавать реальный размер в блоках, но в оригинале почему-то не так
					block1.w[AHDD_PART_W - 1 - OutImgF.nLogDiskNum * 2] = len;
					OutImgF.nLogDiskNum++;
					//////////////////////////////////
				}
				else
				{
					std::_tcout << _T("\nОшибка чтения начального блока раздела ") << nld << std::endl;
					nErrors++;
				}
			}
			else
			{
				std::_tcout << _T("\nОшибка перемещения к начальному блоку раздела ") << nld << std::endl;
				nErrors++;
			}
		}

		//////////////////////////////////
		if (bRet)
		{
			WriteEmptyTail(OutF, &OutImgF);
			block1.w[AHDD_CYL_W] = OutImgF.C;
			block1.w[AHDD_HEAD_W] = OutImgF.H & 0xff; // нужен только младший байт
			block1.w[AHDD_SEC_W] = OutImgF.S;
			block1.w[AHDD_LOGD_W] = (OutImgF.nLogDiskNum & 0xffff) | (boot << 8);
			uint16_t crc = 012701;

			// посчитаем контрольную сумму
			for (int i = 0; i < (OutImgF.nLogDiskNum * 2 + 4); ++i)
			{
				crc += block1.w[AHDD_CYL_W - i];
			}

			block1.w[AHDD_PART_W - OutImgF.nLogDiskNum * 2] = crc;
			fseek(OutF, (AHDD_PT_SEC + (OutImgF.bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET);
			HDIStuff::InverseSector(block1);

			if ((fwrite(block1.b.data(), 1, SECTOR_SIZEB, OutF)) != SECTOR_SIZEB)
			{
				std::_tcout << _T("Ошибка записи в файл ") << strOutImgName << std::endl;
				bRet = false;
			}

			fseek(OutF, ((OutImgF.bHDI) ? SECTOR_SIZEB : 0), SEEK_SET);
			block1.w = g_MBRAltPro;
			HDIStuff::InverseSector(block1);

			if ((fwrite(block1.b.data(), 1, SECTOR_SIZEB, OutF)) != SECTOR_SIZEB)
			{
				std::_tcout << _T("Ошибка записи в файл ") << strOutImgName << std::endl;
				bRet = false;
			}
		}

		//////////////////////////////////
		std::_tcout << _T("\nУспешно сконвертировано ") << (InImgF->nLogDiskNum - nErrors) << _T(" разделов\n");
		fclose(OutF);

		if (nErrors)
		{
			std::_tcout << _T("С ошибками - ") << nErrors << _T(" разделов\n");
		}
	}
	else
	{
		std::_tcout << _T("Ошибка чтения таблицы разделов формата 'Самара'\n");
	}

	return bRet;
}


//
// inf - входной файл образа
// outf - выходной файл образа
// OutImgf - выходной формат
// nLenBlk - размер в блоках
bool BKHDDTool::ConvertLogDiskImage(FILE *inf, FILE *outf, IMGFormat *OutImgf, uint32_t nLenBlk)
{
	bool bRet = true;
	USector buf{};

	for (uint32_t i = 0; i < nLenBlk; ++i)
	{
		buf.b.fill(0); // для начала очистим буфер. если размер файла будет не кратен блоку,
		// остаток сектора должен быть заполнен нулями.

		if (!feof(inf))
		{
			fread(buf.b.data(), 1, SECTOR_SIZEB, inf); // читаем сколько сможем
		}

		if (ferror(inf))
		{
			std::_tcout << _T("\nОшибка чтения из входного образа\n");
			bRet = false;
			break;
		}

		if (SECTOR_SIZEB != fwrite(buf.b.data(), 1, SECTOR_SIZEB, outf))
		{
			std::_tcout << _T("\nОшибка записи в выходной образ\n");
			bRet = false;
			break;
		}

		if (++OutImgf->nSector == OutImgf->S)
		{
			OutImgf->nSector = 0;

			if (++OutImgf->nHead == OutImgf->H)
			{
				OutImgf->nHead = 0;

				if (++OutImgf->nCyl > OutImgf->C)
				{
					// std::_tcout << _T("Переполнение образа диска\n");
					OutImgf->C = OutImgf->nCyl; // корректируем размер в сторону увеличения.
					bRet = false;
				}
			}
		}
	}

	fflush(outf);
	return bRet;
}
