/*
*  работа с образами винчестера БК в форматах "Самара" и "Альтпро"
*  Прога по мотивам исходника bkhdd.cpp (c) 2010 Terra software
*/
// BKHDDTool.cpp: определяет точку входа для консольного приложения.
//

#include "pch.h"

#include "BKHDDTool.h"
#include "getopt.h"
#include "StringUtil.h"
#include "versionNo.h"
#include <clocale>
#include <ctime>
#include <format>

// mbr для Альтпро
std::array<uint16_t, SECTOR_SIZEW> g_MBRAltPro = { 0xA0, 0x15DF, 0xFF87, 0xFFEA, 0x15DF, 0x1C, 0xFFE0, 0x87, 0x0, };

USector g_sector;
ProrgamOptions g_options;

std::_tstring g_HDIModelName = _T("BKEMU HARD DRIVE IMAGE");

static const std::_tstring g_nameAltpro = _T("АльтПро");
static const std::_tstring g_nameSamara = _T("Самара");

static const std::array<std::_tstring, 3> g_pstrExts =
{
	_T(".img"),
	_T(".bkd"),
	_T(".dsk")
};

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	srand((unsigned)time(nullptr));
	setlocale(LC_ALL, "ru_RU");
	int nRetCode = 0;
	FILE *pHDDfile = nullptr;
	g_options.strAppPath = fs::current_path();
	static struct option long_options[] =
	{
		{ _T("help"),        no_argument,       nullptr, _T('?') },
		{ _T("version"),     no_argument,       nullptr, _T('v') },
		{ _T("altpro"),      no_argument,       nullptr, _T('a') },
		{ _T("samara"),      no_argument,       nullptr, _T('m') },
		{ _T("hdi"),         no_argument,       nullptr, _T('i') },
		{ _T("convert"),     no_argument,       nullptr, _T('o') },
		{ _T("path"),        required_argument, nullptr, _T('p') },
		{ _T("cylinder"),    required_argument, nullptr, _T('c') },
		{ _T("head"),        required_argument, nullptr, _T('h') },
		{ _T("sector"),      required_argument, nullptr, _T('s') },
		{ nullptr,           no_argument,       nullptr, 0 }
	};
	static TCHAR optstring[] = _T("?vamiop:c:h:s:");
	int option_index = 0;
	bool bShowHelp = false;
	int c{};

	while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
	{
		// Handle options
		switch (c)
		{
			case _T('?'):
				bShowHelp = true;
				break;

			case _T('v'):
				BKHDDTool::PrintVersion();
				return 0;

			case _T('a'):
				g_options.bAltPro = true;
				g_options.bSamara = false;
				break;

			case _T('m'):
				g_options.bAltPro = false;
				g_options.bSamara = true;
				break;

			case _T('i'):
				g_options.bHDI = true;
				break;

			case _T('p'):
				g_options.strPath = optarg ? fs::path(optarg) : g_options.strAppPath;
				break;

			case _T('o'):
				g_options.bConvert = true;
				break;

			case _T('c'):
				if (optarg)
				{
					auto strTmp = std::_tstring(optarg);
					g_options.C = std::stol(strTmp) & 0xffff;
				}

				g_options.bCreate = true;
				break;

			case _T('h'):
				if (optarg)
				{
					auto strTmp = std::_tstring(optarg);
					g_options.H = std::stol(strTmp) & 0xffff;
				}

				g_options.bCreate = true;
				break;

			case _T('s'):
				if (optarg)
				{
					auto strTmp = std::_tstring(optarg);
					g_options.S = std::stol(strTmp) & 0xffff;
				}

				g_options.bCreate = true;
				break;
		}

		if (bShowHelp)
		{
			BKHDDTool::Usage();
			return 0;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc < 1)
	{
		BKHDDTool::Usage();
	}
	else
	{
		g_options.strImgName = fs::path(argv[0]);

		if (g_options.bCreate)
		{
			if (!g_options.strImgName.has_extension())
			{
				g_options.strImgName.replace_extension(_T(".img"));
			}

			if (g_options.bHDI)
			{
				// если создаём hdi файл, то подменим расширение.
				g_options.strImgName.replace_extension(_T(".hdi"));
			}

			// создаём образ
			pHDDfile = _tfopen(g_options.strImgName._tstring().c_str(), _T("wb"));

			if (pHDDfile)
			{
				IMGFormat format;
				format.bHDI = g_options.bHDI;
				format.bSamara = g_options.bSamara;
				format.C = g_options.C;
				format.H = g_options.H;
				format.S = g_options.S;
				format.nLogDiskNum = 0;

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

					if (SECTOR_SIZEB != fwrite(g_sector.b.data(), 1, SECTOR_SIZEB, pHDDfile))
					{
						std::_tcout << _T("Не удалось записать HDI сектор в файл '") << g_options.strImgName << _T("'\n");
					}
				}

				BKHDDTool::AssemblyImage(pHDDfile, &format);
				fclose(pHDDfile);
			}
			else
			{
				std::_tcout << _T("Не удалось создать файл '") << g_options.strImgName << _T("'\n");
				nRetCode = 1;
			}
		}
		else
		{
			// разбираем образ
			pHDDfile = _tfopen(g_options.strImgName._tstring().c_str(), _T("rb"));

			if (pHDDfile)
			{
				IMGFormat format;

				if (BKHDDTool::AnalyseImage(pHDDfile, &format))
				{
					if (g_options.bConvert)
					{
						if (format.bSamara)
						{
							BKHDDTool::ConvertSamara2Altpro(pHDDfile, &format);
						}
						else
						{
							BKHDDTool::ConvertAltPro2Samara(pHDDfile, &format);
						}
					}
					else
					{
						if (format.bSamara)
						{
							BKHDDTool::SplitSamara(pHDDfile, &format);
						}
						else
						{
							BKHDDTool::SplitAltPro(pHDDfile, &format);
						}
					}
				}

				fclose(pHDDfile);
			}
			else
			{
				std::_tcout << _T("Не удалось открыть файл '") << g_options.strImgName << _T("'\n");
				nRetCode = 1;
			}
		}
	}

	return nRetCode;
}

void BKHDDTool::PrintInfo(IMGFormat *imgf)
{
	std::_tcout << _T("Тип образа: ") << (imgf->bHDI ? _T("HDI") : _T("IMG")) << std::endl;
	std::_tcout << _T("Формат образа: ") << (imgf->bSamara ? g_nameSamara : g_nameAltpro) << std::endl;
	std::_tcout << _T("Геометрия\nКоличество цилиндров: ") << imgf->C << std::endl;
	std::_tcout << _T("Количество головок:   ") << imgf->H << std::endl;
	std::_tcout << _T("Количество секторов:  ") << imgf->S << std::endl;
}

bool BKHDDTool::AssemblyImage(FILE *f, IMGFormat *imgf)
{
	PrintInfo(imgf);
	MakeOutDir(true);
	const bool bRet = imgf->bSamara ? AssemblySamara(f, imgf) : AssemblyAltPro(f, imgf);
	return bRet;
}

bool BKHDDTool::AnalyseImage(FILE *f, IMGFormat *imgf)
{
	if (CheckFormat_hdd(f, imgf))
	{
		PrintInfo(imgf);
		std::_tcout << _T("\nКоличество разделов: ") << imgf->nLogDiskNum << std::endl;
		MakeOutDir();
		return true;
	}

	if (imgf->nIOStatus == IMGIOSTATUS::IO_ERROR)
	{
		std::_tcout << _T("Ошибка чтения файла!\n");
	}
	else
	{
		std::_tcout << _T("Формат нераспознан!\n");
	}

	return false;
}

void BKHDDTool::ShowMsg1(const std::_tstring &n1, const std::_tstring &n2)
{
	std::_tcout << _T("Формат образа опознан как '") << n1 << _T("', но в опциях задано обрабатывать как '") << n2 << _T("'\n");
	std::_tcout << _T("Образ будет обработан как '") << n1 << _T("'\n");
}

void BKHDDTool::ShowMsg2(const std::_tstring &n1)
{
	std::_tcout << _T("Формат образа не опознан, но в опциях задано обрабатывать как '") << n1 << _T("'\n");
	std::_tcout << _T("Образ будет обработан как '") << n1 << _T("'\n");
}

// проверка формата и заодно определение геометрии
// выход: true - параметры структуры IMGFormat достоверны
//       false - нет. формат не распознан
bool BKHDDTool::CheckFormat_hdd(FILE *f, IMGFormat *imgf)
{
	if (fread(g_sector.b.data(), 1, SECTOR_SIZEB, f) == SECTOR_SIZEB)  // прочтём первый сектор.
	{
		// подсчитаем контрольную сумму
		if (HDIStuff::CheckCS(&g_sector))
		{
			imgf->bHDI = true; // если первый сектор правильный - то это вероятно образ HDI
		}
		else
		{
			imgf->bHDI = false; // если нет - то это просто IMG
			fseek(f, 0, SEEK_SET); // вернёмся к началу образа.
		}
	}
	else
	{
		imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
		return false;
	}

	IMGFormat aimgf{ *imgf };
	IMGFormat simgf{ *imgf };
	bool bSamara = false;
	bool bAltPro = HDIStuff::CheckAltPro(f, &aimgf);  // проверка на альтпро.

	if (aimgf.nIOStatus == IMGIOSTATUS::IO_ERROR)
	{
		return false;
	}

	if (bAltPro)
	{
		*imgf = aimgf;
	}
	else
	{
		bSamara = HDIStuff::CheckSamara(f, &simgf);   // проверка на формат Самара

		if (simgf.nIOStatus == IMGIOSTATUS::IO_ERROR)
		{
			return false;
		}

		if (bSamara)
		{
			*imgf = simgf;
		}
	}

	bool bRet = bAltPro || bSamara;

	if (bSamara && g_options.bAltPro)
	{
		ShowMsg1(g_nameSamara, g_nameAltpro);
	}
	else if (bAltPro && g_options.bSamara)
	{
		ShowMsg1(g_nameAltpro, g_nameSamara);
	}

	if (!bRet) // образ никак неопознан.
	{
		if (g_options.bAltPro)
		{
			ShowMsg2(g_nameAltpro);
			g_options.bSamara = false;
			*imgf = aimgf;
			bRet = true;
		}
		else if (g_options.bSamara)
		{
			ShowMsg2(g_nameSamara);
			g_options.bSamara = true;
			*imgf = simgf;
			bRet = true;
		}
	}

	return bRet;
}

// разбираем образ винта альтпро на лог.диски
bool BKHDDTool::SplitAltPro(FILE *f, IMGFormat *imgf)
{
	bool bRet = false;
	int nErrors = 0;
	imgf->nIOStatus = IMGIOSTATUS::IO_OK;

	// прочитаем сектор 8, нам нужна таблица разделов.
	if ((fseek(f, (AHDD_PT_SEC + (imgf->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
	        && (fread(g_sector.b.data(), 1, SECTOR_SIZEB, f) == SECTOR_SIZEB))
	{
		HDIStuff::InverseSector(g_sector);

		for (int nld = 0; nld < imgf->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;
			}

			uint16_t nHead = nCyl & 0xF; // номер головки из таблицы разделов
			nCyl >>= 4; // номер дорожки из таблицы разделов
			uint32_t lba = (nCyl * imgf->H + nHead) * imgf->S; // рассчитываем начало раздела в блоках
			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 + (imgf->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
			{
				// переместимся к началу раздела
				if (!(bRet = WriteLogDiskImage(f, nld, nLen)))
				{
					std::_tcout << _T("\nОшибка извлечения раздела ") << nld << std::endl;
					nErrors++;
				}
			}
			else
			{
				std::_tcout << _T("\nОшибка перемещения к разделу ") << nld << std::endl;
				nErrors++;
			}
		}

		std::_tcout << _T("\nУспешно извлечено ") << (imgf->nLogDiskNum - nErrors) << _T(" разделов\n");

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

	return bRet;
}

// разбираем образ винта самара на лог.диски
bool BKHDDTool::SplitSamara(FILE *f, IMGFormat *imgf)
{
	bool bRet = false;
	int nErrors = 0;
	USector buf{};
	imgf->nIOStatus = IMGIOSTATUS::IO_OK;

	// прочитаем сектор 2, нам нужна таблица разделов.
	if ((fseek(f, (SHDD_PT_SEC + (imgf->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
	        && (fread(g_sector.b.data(), 1, SECTOR_SIZEB, f) == SECTOR_SIZEB))
	{
		HDIStuff::InverseSector(g_sector);

		for (int nld = 0; nld < imgf->nLogDiskNum; ++nld)
		{
			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 + (imgf->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET) == 0)
			{
				// читаем начальный блок
				if ((fread(buf.b.data(), 1, SECTOR_SIZEB, f)) == SECTOR_SIZEB)
				{
					HDIStuff::InverseSector(buf);
					uint16_t nLen = buf.w[SHDD_LD_LEN_W];
					uint8_t num = buf.b[SHDD_NLD_W];
					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 ");
					}

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

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

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

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

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

					// переместимся к началу раздела
					if (!(bRet = WriteLogDiskImage(f, nld, nLen)))
					{
						std::_tcout << _T("\nОшибка извлечения раздела ") << nld << std::endl;
						nErrors++;
					}
				}
				else
				{
					std::_tcout << _T("\nОшибка чтения начального блока раздела ") << nld << std::endl;
					nErrors++;
				}
			}
			else
			{
				std::_tcout << _T("\nОшибка перемещения к начальному блоку раздела ") << nld << std::endl;
				nErrors++;
			}
		}

		std::_tcout << _T("\nУспешно извлечено ") << (imgf->nLogDiskNum - nErrors) << _T(" разделов\n");

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

	return bRet;
}

CBKParseImage g_ImgParser;

void BKHDDTool::PrintOSType(const PARSE_RESULT &pr)
{
	const std::_tstring strOS = CBKParseImage::GetOSName(pr.imageOSType);
	std::_tcout << strOS << std::endl;
}

// запись раздела из образа винчестера в отдельный образ
bool BKHDDTool::WriteLogDiskImage(FILE *f, int nld, uint32_t nLen)
{
	bool bRet = true;
	USector buf{};
	// сформируем очередное имя
	std::_tstring pOutName = std::format(_T("{:03d}_part.bkd"), nld);
	FILE *outf = _tfopen(pOutName.c_str(), _T("wb"));

	if (outf)
	{
		for (uint32_t i = 0; i < nLen; ++i)
		{
			if (SECTOR_SIZEB == fread(buf.b.data(), 1, SECTOR_SIZEB, f))
			{
				HDIStuff::InverseSector(buf);

				if (SECTOR_SIZEB != fwrite(buf.b.data(), 1, SECTOR_SIZEB, outf))
				{
					std::_tcout << _T("\nОшибка записи в образ FDD ") << pOutName << std::endl;
					bRet = false;
					break;
				}
			}
			else
			{
				std::_tcout << _T("\nОшибка чтения из образа HDD ") << g_options.strImgName << std::endl;
				bRet = false;
				break;
			}
		}

		fclose(outf);

		if (bRet)
		{
			PARSE_RESULT pr = g_ImgParser.ParseImage(fs::path(pOutName), 0);
			std::_tcout << _T("ОС: ");
			PrintOSType(pr);
		}
	}
	else
	{
		bRet = false;
	}

	return bRet;
}

// запись раздела из отдельного образа в образ винчестера
bool BKHDDTool::ReadLogDiskImage(FILE *f, IMGFormat *imgf, const fs::path &strName)
{
	bool bRet = true;
	USector buf{};
	PARSE_RESULT pr = g_ImgParser.ParseImage(strName, 0);

	if (pr.imageOSType != IMAGE_TYPE::ERROR_NOIMAGE)
	{
		unsigned long len = (pr.nImageSize + (SECTOR_SIZEB - 1)) / SECTOR_SIZEB; // размер файла в блоках
		// если ОС раздела не опознана, то берём размер файла в блоках, иначе - берём размер из ОС
		unsigned long partLen = (pr.imageOSType == IMAGE_TYPE::UNKNOWN) ? len : pr.nPartLen;

		// если размер оказался нулевым
		if (partLen == 0)
		{
			partLen = 1600; // заменим размером по умолчанию
		}

		FILE *inf = _tfopen(strName._tstring().c_str(), _T("rb"));

		if (inf)
		{
			std::_tcout << _T("Размер файла в блоках: ") << len << _T(", размер раздела : ") << partLen << _T(", ОС : ");
			PrintOSType(pr);

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

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

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

				HDIStuff::InverseSector(buf);

				if (SECTOR_SIZEB != fwrite(buf.b.data(), 1, SECTOR_SIZEB, f))
				{
					imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
					std::_tcout << _T("\nОшибка записи в образ HDD ") << g_options.strImgName << std::endl;
					bRet = false;
					break;
				}

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

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

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

			fclose(inf);
		}
		else
		{
			bRet = false;
		}
	}
	else
	{
		bRet = false;
	}

	return bRet;
}

void BKHDDTool::MakeOutDir(bool bChangeOnly)
{
	if (!g_options.strPath.empty()) // если путь задали
	{
		if (g_options.strPath.is_relative()) // если задан относительный путь
		{
			g_options.strPath = g_options.strAppPath / g_options.strPath; // делаем абсолютный
		}

		if (!bChangeOnly)
		{
			if (!fs::exists(g_options.strPath))
			{
				std::error_code ec;

				if (!fs::create_directory(g_options.strPath, ec))
				{
					std::_tcout << _T("Не удалось создать директорию '") << g_options.strPath << _T("' :");
					std::cout << ec.message() << std::endl;
				}
			}
		}

		std::error_code ec;
		fs::current_path(g_options.strPath, ec);

		if (ec)
		{
			std::_tcout << _T("Не удалось найти директорию ") << g_options.strPath << std::endl;
		}
	}
}

// запись пустых секторов от текущего места до конца дорожки.
// либо, если указано количество nWriteNum - заданное кол-во от текущего места
bool BKHDDTool::WriteEmptySectors(FILE *f, IMGFormat *imgf, int nWriteNum)
{
	bool bRet = true;
	USector buf{};
	buf.b.fill(0377);
	int nStart{}, nStop{};

	if (nWriteNum == 0)
	{
		nStart = imgf->nSector;

		if (nStart == 0) // если и так на начале дорожки, то ничего делать не надо.
		{
			return true;
		}

		nStop = imgf->S;
	}
	else
	{
		nStart = 0;
		nStop = nWriteNum;
	}

	for (int i = nStart; i < nStop; ++i)
	{
		if ((fwrite(buf.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
		{
			imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
			std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("'\n");
			return false;
		}

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

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

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

	return bRet;
}

// если задано достаточно большое кол-во дорожек, и за концом разделов есть место
// то надо забить его нулями
void BKHDDTool::WriteEmptyTail(FILE *f, IMGFormat *imgf)
{
	USector buf{};
	buf.b.fill(0377);

	while (true)
	{
		if ((fwrite(buf.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
		{
			imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
			std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("'\n");
			break;
		}

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

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

				if (++imgf->nCyl > imgf->C)
				{
					break;
				}
			}
		}
	}
}


bool BKHDDTool::AssemblyAltPro(FILE *f, IMGFormat *imgf)
{
	bool bRet = false;
	// в g_sector будем формировать таблицу разделов.
	g_sector.b.fill(0);
	imgf->nCyl = 0;
	imgf->nHead = 0;
	imgf->nSector = 0;
	imgf->nLogDiskNum = 0;
	uint16_t boot = 2;
	WriteEmptySectors(f, imgf, imgf->S); // запишем пустую нулевую дорожку
	std::vector <fs::path> aFileList;
	std::error_code ec;

	for (auto const &it : fs::directory_iterator(g_options.strPath, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink, ec))
	{
		const fs::path &file = it.path(); // имя найденного файла

		if (fs::is_regular_file(file))
		{
			auto pExt =  strUtil::strToLower(file.extension()._tstring());

			for (const auto &pstrExt : g_pstrExts)
			{
				if (pstrExt == pExt)
				{
					aFileList.emplace_back(file);
					break;
				}
			}
		}
	}

	if (!aFileList.empty())
	{
		// отсортируем найденное в порядке возрастания
		std::sort(aFileList.begin(), aFileList.end());

		// затем по порядку всё это и обработаем
		for (auto &file : aFileList)
		{
			uint32_t beginLba = (imgf->nCyl * imgf->H + imgf->nHead) * imgf->S;
			g_sector.w[AHDD_PART_W - imgf->nLogDiskNum * 2] = (imgf->nHead & 0xf) | (imgf->nCyl << 4);
			_tprintf(_T("Раздел: %03d"), imgf->nLogDiskNum);
			std::_tcout << _T(", файл: ") << file.filename().make_preferred() << std::endl;

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

			bRet = ReadLogDiskImage(f, imgf, file);
			bRet = WriteEmptySectors(f, imgf);
			uint32_t endLba = (imgf->nCyl * imgf->H + imgf->nHead) * imgf->S;
			uint32_t len = endLba - beginLba;
			// размер высчитывается вместе с пустым пространством, выравнивающим разделы по началу дорожки
			// хотя никто не мешает задавать реальный размер в блоках, но в оригинале почему-то не так
			g_sector.w[AHDD_PART_W - 1 - imgf->nLogDiskNum * 2] = len;
			imgf->nLogDiskNum++;
		}

		aFileList.clear();
	}

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

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

		g_sector.w[AHDD_PART_W - imgf->nLogDiskNum * 2] = crc;
		fseek(f, (AHDD_PT_SEC + (imgf->bHDI ? 1 : 0)) * SECTOR_SIZEB, SEEK_SET);
		HDIStuff::InverseSector(g_sector);

		if ((fwrite(g_sector.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
		{
			imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
			std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("'\n");
			bRet = false;
		}

		fseek(f, (imgf->bHDI ? SECTOR_SIZEB : 0), SEEK_SET);
		g_sector.w = g_MBRAltPro;
		HDIStuff::InverseSector(g_sector);

		if ((fwrite(g_sector.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
		{
			imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
			std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("'\n");
			bRet = false;
		}
	}

	return bRet;
}

bool BKHDDTool::AssemblySamara(FILE *f, IMGFormat *imgf)
{
	bool bRet = false;
	USector buf{};
	// в g_sector будем формировать таблицу разделов.
	g_sector.b.fill(0);
	imgf->nCyl = 0;
	imgf->nHead = 0;
	imgf->nSector = 0;
	imgf->nLogDiskNum = 0;
	uint16_t boot = 2;
	uint32_t lba = 3;
	WriteEmptySectors(f, imgf, lba); // сперва запишем 3 пустых сектора.
	std::vector <fs::path> aFileList;
	std::error_code ec;

	for (auto const &it : fs::directory_iterator(g_options.strPath, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink, ec))
	{
		const fs::path &file = it.path(); // имя найденного файла

		if (fs::is_regular_file(file))
		{
			auto pExt = strUtil::strToLower(file.extension()._tstring());

			for (const auto &pstrExt : g_pstrExts)
			{
				if (pstrExt == pExt)
				{
					aFileList.emplace_back(file);
					break;
				}
			}
		}
	}

	if (!aFileList.empty())
	{
		// отсортируем найденное в порядке возрастания
		std::sort(aFileList.begin(), aFileList.end());

		// затем по порядку всё это и обработаем
		for (auto &file : aFileList)
		{
			long nBlkLen = static_cast<long>(fs::file_size(file));
			// сперва приходится высчитать будущий размер тут
			// а потом то же самое, в  функции ReadLogDiskImage - нерационально
			nBlkLen = (nBlkLen + (SECTOR_SIZEB - 1)) / SECTOR_SIZEB; // размер файла в блоках

			if (nBlkLen < 12)
			{
				std::_tcout << _T("ВНИМАНИЕ: размер образа '") << file.filename().make_preferred() << _T("' меньше 12 блоков (") << nBlkLen << _T(").\nПропускаем такой образ.\n");
				continue;
			}

			if (nBlkLen > 0xffff)
			{
				std::_tcout << _T("ВНИМАНИЕ: размер образа '") << file.filename().make_preferred() << _T("' превышает 65535 блоков (") << nBlkLen << _T(").\nПропускаем такой образ.\n");
				continue;
			}

			// размер раздела не может быть меньше 1600 блоков. это просто нерационально.
			// !!! может-может. В ксидос может.
			uint16_t len = nBlkLen & 0xffff;
			// сперва надо создать начальный сектор
			buf.b.fill(0);
			buf.w[SHDD_NLD_W] = imgf->nLogDiskNum + 2;      // номер ld
			buf.w[SHDD_LD_LEN_W] = len;     // длина ld
			buf.w[SHDD_LD_FLAGS_W] = 0;     // флаги - все нули
			buf.w[SHDD_ADR_BOOT_W] = 02000; // адрес загрузки загрузчика :)
			buf.w[SHHD_ADR_PAR_W] = 01000;  // адрес блока параметров для загрузчика
			buf.w[SHDD_PAGE_W] = 014400;    // состояние регистра страниц
			g_sector.w[SHDD_PART_W + imgf->nLogDiskNum * 2] = lba & 0xffff;
			g_sector.w[SHDD_PART_W + 1 + imgf->nLogDiskNum * 2] = (lba >> 16) & 0xffff;
			HDIStuff::InverseSector(buf);

			if ((fwrite(buf.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
			{
				imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
				std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("' \n");
				bRet = false;
			}

			_tprintf(_T("Раздел: %03d, файл: "), imgf->nLogDiskNum);
			std::_tcout << file.filename().make_preferred() << std::endl;
			bRet = ReadLogDiskImage(f, imgf, file);
			lba += len + 1;
			imgf->nLogDiskNum++;
		}

		aFileList.clear();
	}

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

		if ((fwrite(g_sector.b.data(), 1, SECTOR_SIZEB, f)) != SECTOR_SIZEB)
		{
			imgf->nIOStatus = IMGIOSTATUS::IO_ERROR;
			std::_tcout << _T("Ошибка записи в файл '") << g_options.strImgName << _T("' \n");
			bRet = false;
		}
	}

	return bRet;
}

void BKHDDTool::Usage()
{
	std::_tcout << (_T(FILE_PRODUCT_STR"\n") \
	                _T("(c) 2010 Terra software, 2016-2026 gid\n") \
	                _T("\n") \
	                _T("Использование:\n") \
	                _T("BKHDDtool -? (--help)\n") \
	                _T("  Вывод этой справки.\n\n") \
	                _T("BKHDDtool -v (--version)\n") \
	                _T("  Показать информацию о версии.\n\n") \
	                _T("BKHDDtool [-a | -m][-i][-o][-p<Путь>][-c<N> -h<N> -s<N>] <Имя образа HDD>\n") \
	                _T("  -a (--altpro) - использовать формат образа диска 'АльтПро'\n") \
	                _T("  -m (--samara) - использовать формат образа диска 'Самара'\n") \
	                _T("    Если не задана ни одна опция, по умолчанию используется формат 'АльтПро'.\n") \
	                _T("  -i (--hdi) - создавать образ в формате hdi. При разборке ключ игнорируется.\n") \
	                _T("    При преобразовании ключ игнорируется.\n") \
	                _T("  -o (--convert) - преобразование образа из одного формата в другой.\n") \
	                _T("    Если ключ задан - преобразование, при этом ключ -p игнорируется, если\n") \
	                _T("    не задан - разборка.\n") \
	                _T("  -p<Путь> (--path <Путь>) - директория, где искать образы дискет для\n") \
	                _T("    сборки образа винчестера, или куда сохранять образы дискет при разборке\n") \
	                _T("    образа винчестера.\n") \
	                _T("  -c<N> (--cylinder <N>) - количество цилиндров (дорожек)\n") \
	                _T("  -h<N> (--head <N>) - количество головок\n") \
	                _T("  -s<N> (--sector <N>) - количество секторов на дорожке\n") \
	                _T("    Если хоть один из этих ключей задан, то считается, что выбран режим сборки\n") \
	                _T("    образа, при этом ключ -o игнорируется, ключи -a или -m задают формат\n") \
	                _T("    образа.\n") \
	                _T("  <Имя образа HDD> - возможно задать с полным путём к файлу.\n") \
	                _T("\n") \
	                _T("Программа может работать в трёх режимах:\n") \
	                _T("1. Разборка образа на логические диски.\n") \
	                _T("BKHDDtool [-a | -m][-p<Путь>] <Имя образа HDD>\n") \
	                _T("  Задавать формат не обязательно, он определяется автоматически. Но если\n") \
	                _T("формат не опознан, то образ будет обрабатываться в заданном формате.\n") \
	                _T("АльтПро - формат по умолчанию, если ключи не заданы\n") \
	                _T("\n") \
	                _T("2. Сборка образов из логических дисков.\n") \
	                _T("BKHDDtool [-a | -m][-i][-p<Путь>][-c<N> -h<N> -s<N>] <Имя образа HDD>\n") \
	                _T("  Если задан хоть один ключ, определяющий геометрию, то это означает создание\n") \
	                _T("образа винчестера из образов дискет.\n") \
	                _T("\n") \
	                _T("3. Преобразование образов из одного формата в другой.\n") \
	                _T("BKHDDtool [-a | -m] -o <Имя образа HDD>\n") \
	                _T("  Производится преобразование формата образа 'АльтПро'<->'Самара'\n") \
	                _T("Формат образа определяется автоматически, и в соответствии с этим преобразуется.\n") \
	                _T("\n") \
	                _T("  Если ключ -p не задан, то используется та же директория, где находится образ.\n") \
	                _T("  При разборке создаются файлы nnn_part.bkd, где nnn - номера разделов в том\n") \
	                _T("порядке, в котором они находились в образе винчестера.\n") \
	                _T("  При сборке образы разделов сортируются в алфавитном порядке, и в таком же\n") \
	                _T("порядке размещаются в образе винчестера.\n") \
	                _T("\n") \
	                _T("После создания образ винчестера можно записать на винчестер или карту памяти\n") \
	                _T("с помощью бесплатной утилиты dd -http://www.chrysocome.net/dd\n"));
}

void BKHDDTool::PrintVersion()
{
	std::_tcout << _T(FILE_PRODUCT_STR"\n");
	const auto strVers = std::format(_T("Версия: {}.{}.{}.{}\n"), _T(VERSION_MAJ_STR), _T(VERSION_MIN_STR), _T(VERSION_REV_STR), _T(VERSION_BLD_STR));
	std::_tcout << strVers;
	// ревизия и билд могут начинаться с нуля, но они тем не менее десятичные числа
	// поэтому слегка поизвращаемся.
	std::_tstring str = _T(VERSION_MIN_STR);
	const int vmin = std::stol(str, nullptr, 10);
	str = _T(VERSION_REV_STR);
	const int rev = std::stol(str, nullptr, 10);
	str = _T(VERSION_BLD_STR);
	const int bld = std::stol(str, nullptr, 10);
	const auto strDate = std::format(_T(" {:02d}-{:02d}-{:04d}"), (rev / 100), vmin, (2000 + VERSION_MAJ));
	const auto strTime = std::format(_T(" {:02d}:{:02d}:{:02d}"), (rev % 100), (bld / 100), (bld % 100));
	std::_tcout << _T("Дата сборки:") << strDate << strTime << std::endl;
}

/*
Структура диска альтпро.
Блоки 0..6 (секторы 1..7) - резидентные модули и т.п.
Блок 7 (Сектор 8) - таблица разделов и разные полезные данные.
формат сектора 8:
имя      | смещение | размер | назначение
------------------------------------------------
HD$SPD   | 0        | 2      | признак быстрого чтения (125252)
         | 2        | 766    | область таблицы разделов (помещается 125. лог дисков и
         |          |        | 1 слово - контрольная сумма, вместе с геометрией)
HD$LOG   | 770      | 1      | всего лог.дисков
HD$UNI   | 771      | 1      | # устр. для загрузки по умолч. (0 - А, 2 - С ...)
HD$SEC   | 772      | 2      | секторов на дорожке
HD$HEA   | 774      | 1      | всего головок
HD$DRV   | 775      | 1      | код # привода,уст.внешним драйвером
HD$TRK   | 776      | 2      | всего дорожек

таблица разделов как стек растёт с верхних адресов к нижним.
Записи о логических дисках идут по порядку, начиная с логического диска с номером 0.
начинается с 766, заканчивается 2
Формат записи следующий (отсчёт естественно в направлении от HD$LOG-2 к HD$SPD):
1 слово - номер головки (младшие 4 бита) и номер дорожки (остальные 12 бит)
          ограничение: макс. 4096 из 65536 возможных.
  если это слово < 0 (т.е. на дорожки отводится уже 11 бит.)
          ограничение стало: макс. 2048 из 65536 возможных.
  то раздел считается защищённым от записи.
  это слово не просто <0, оно всё целиком инвертировано!
  разделы выравниваются по началу цилиндра, т.е. начало всегда сектор 1.

2 слово - объём логического диска в блоках, т.е. 65536 = макс. 32Мб.
(предполагается, что каждый лог. диск начинается с первого сектора дорожки, определённой первым словом).

--------------------------------------------------------------------------
Структура диска самара.
первые три блока - МБР
Блок 0 (сектор 1) - не используется, зарезервировано
Блок 1 (сектор 2) - геометрия и таблица разделов
Блок 2 (сектор 3) - не используется, зарезервировано

Блок1
смещение | размер | назначение
------------------------------------------------
0        | 2      | # устр. для загрузки по умолч. (0 - А, 2 - С ...)
2        | 2      | объём цилиндра (общее количество секторов на дорожке) == H * S
4        | 1      | количество секторов на дорожке
5        | 1      | номер последней головки (H - 1)
6        | x      | таблица разделов, максимум 64 раздела (пока будем считать что это максимум)

формат элемента таблицы разделов:
смещение | размер | назначение
------------------------------------------------
0        | 4      | номер логического блока начала раздела (в реальности используется 20 битное слово)


каждый логический диск начинается с начального блока
формат:
смещение | размер | назначение
------------------------------------------------
0        | 1      | номер лог. диска (0 - А, 2 - С ...)
1        | 1      | ? не используется.
2        | 2      | размер лог. диска в блоках
4        | 1      | флаги - признаки
5        | 1      | ? не используется.
6        | 2      | адрес загрузки загрузчика лог. диска
10       | 2      | адрес блока параметров для загрузчика
12       | 2      | состояние регистра страниц

; ФОРМАТ БАЙТА ПРИЗНАКОВ ДИСКА
;1-битые сектора
;2-защ от записи
;4-защ от чтения
;10-полн.защ от записи
;20-полн.защ от чтения
;40
;100
;200

*/

