#include "StdAfx.h"
#include "BKParseImage.h"
#include "BKDRT11Header.h"

CBKParseImage::CBKParseImage(void)
{
}

CBKParseImage::~CBKParseImage(void)
{
}

const char *CBKParseImage::strID_Andos = "ANDOS ";
const char *CBKParseImage::strID_DXDOS = "DX-DOS";
const char *CBKParseImage::strID_FAT12 = "FAT12   ";
const char *CBKParseImage::strID_Aodos = " AO-DOS ";
const char *CBKParseImage::strID_Nord = "NORD ";
const char *CBKParseImage::strID_RT11 = "?BOOT-";
const char *CBKParseImage::strID_RT11Q = "?QUBOOT-";

/*определение типа образа, на выходе номер, соответствующий определённой ОС
    0 - образ неопознан, -1 - ошибка
*/
PARSE_RESULT CBKParseImage::ParseImage(const CString &fname, ULONGLONG nBaseOffset)
{
	union
	{
		BYTE pSector[512];
		WORD wSector[256];
	};
	CFile ImgFile;
	PARSE_RESULT ret;
	BOOL bAC1 = FALSE; //условие андос 1 (метка диска)
	BOOL bAC2 = FALSE; //условие андос 2 (параметры диска)

	if (!ImgFile.Open(fname, CFile::modeRead | CFile::shareDenyWrite))
	{
		ret.imageOSType = IMAGE_TYPE_ERROR;
		return ret;
	}

	ret.strName = fname;
	ret.nBaseOffset = nBaseOffset;
	ret.imageSize = ImgFile.GetLength(); //получим размер файла
	ImgFile.Seek(nBaseOffset, CFile::begin);

	if (ImgFile.Read(pSector, 512) < 512)
	{
		ret.imageOSType = IMAGE_TYPE_ERROR;
	}
	else
	{
		// Получим признак системного диска
		if (wSector[0] == 0240 /*NOP*/)
			if (wSector[1] != 5 /*RESET*/)
			{
				ret.bImageBootable = TRUE;
			}

		//проверим тут, а то потом, прямо в условии присваивания не работают из-за оптимизации проверки условий
		bAC1 = (memcmp(strID_Andos, (char *)(pSector + 04), strlen(strID_Andos)) == 0);
		//секторов в кластере       число файлов в корне            медиадескриптор             число секторов в одной фат
		bAC2 = ((pSector[015] == 4) && (*(WORD *)(pSector + 021) == 112) && (pSector[025] == 0371) && (*(WORD *)(pSector + 026) == 2));

		// Узнаем формат образа
		if (wSector[0400 / 2] == 0123456) //если микродос
		{
			//определяем с определённой долей вероятности систему аодос
			if (pSector[4] == 032 && (wSector[6 / 2] == 256 || wSector[6 / 2] == 512))
			{
				//либо расширенный формат
				ret.imageOSType = IMAGE_TYPE_AODOS;
			}
			else if (memcmp(strID_Aodos, (char *)(pSector + 4), strlen(strID_Aodos)) == 0)
			{
				//либо по метке диска
				//аодос 1.77 так не распознаётся. его будем считать просто микродосом. т.к. там нету директорий
				ret.imageOSType = IMAGE_TYPE_AODOS;
			}
			else if (memcmp(strID_Nord, (char *)(pSector + 4), strlen(strID_Nord)) == 0)
			{
				//предполагаем, что это метка для системы
				ret.imageOSType = IMAGE_TYPE_NORD;
			}
			/*обнаружилась нехорошая привычка AODOS и возможно NORD мимикрировать под MKDOS,
			так что проверка на него - последней, при этом, т.к. MKDOSу похрен на ячейки 04..012
			то иногда, если там были признаки аодоса, мкдосный диск принимается за аодосный.
			но это лучше, чем наоборот, ибо в первом случае просто в имени директории будет глючный символ,
			а во втором - вообще все директории пропадают.
			*/
			else if (wSector[0402 / 2] == 051414)
			{
				ret.imageOSType = IMAGE_TYPE_MKDOS;
			}
			//тут надо найти способ как определить прочую экзотику
			else
			{
				ret.imageOSType = IMAGE_TYPE_MIKRODOS;
			}
		}
		//проверка на формат FAT12, если надпись "FAT12"
		//однако, в некоторых андосах, нету идентификатора фс, зато есть метка андос,
		//но бывает, что и метки нету, зато параметры BPB специфические
		else if ( //если хоть что-то выполняется, то скорее всего это FAT12, но возможны ложные срабатывания из-за мусора в этой области
		    bAC1 || bAC2 ||
		    (memcmp(strID_FAT12, (char *)(pSector + 066), strlen(strID_FAT12)) == 0)
		)
		{
			BYTE nMediaDescriptor = pSector[025];
			int nBootSize = *(WORD *)(pSector + 016) * (*(WORD *)(pSector + 013));
			ImgFile.Seek(nBaseOffset + nBootSize, CFile::begin);
			BYTE b0, b1;
			ImgFile.Read(&b0, 1);
			ImgFile.Read(&b1, 1);

			if (b1 == 0xff && b0 == nMediaDescriptor) //если медиадескриптор в BPB и фат совпадают, то это точно FAT12
			{
				if (bAC1 || bAC2) //если одно из этих условий - то это скорее всего ANDOS
				{
					ret.imageOSType = IMAGE_TYPE_ANDOS;
				}
				else if (memcmp(strID_DXDOS, (char *)(pSector + 04), strlen(strID_DXDOS)) == 0) //если метка диска DX-DOS, то это скорее всего DX-DOS, других вариантов пока нет.
				{
					ret.imageOSType = IMAGE_TYPE_DXDOS;
				}
				else
				{
					ret.imageOSType = IMAGE_TYPE_MSDOS; //тут только MS DOS остался
				}
			}
			else
			{
				ret.imageOSType = IMAGE_TYPE_UNKNOWN;
			}
		}
		//проверка на предположительно возможно НС-ДОС
		else if (wSector[2 / 2] == 012700 && wSector[4 / 2] == 0404 && wSector[6 / 2] == 0104012)
		{
			//нс-дос никак себя не выделяет среди других ос, поэтому будем её определять по коду.
			ret.imageOSType = IMAGE_TYPE_NCDOS;
		}
		else
		{
			//дальше сложные проверки
			//проверим, а не рт11 ли у нас
			int t = IsRT11_old(&ImgFile, nBaseOffset, pSector);

			switch (t)
			{
				case 0:
				{
					t = IsRT11(&ImgFile, nBaseOffset, pSector);

					switch (t)
					{
						case 0:
						{
							//нет, не рт11
							//проверим, а не ксидос ли у нас
							t = IsCSIDOS3(&ImgFile, nBaseOffset, pSector);

							switch (t)
							{
								case 0:
									ret.imageOSType = IMAGE_TYPE_UNKNOWN;
									break;

								case 1:
									ret.imageOSType = IMAGE_TYPE_CSIDOS3;
									break;

								default:
									ret.imageOSType = IMAGE_TYPE_ERROR;
							}

							break;
						}

						case 1:
						{
							ret.imageOSType = IMAGE_TYPE_RT11;
							break;
						}

						default:
							ret.imageOSType = IMAGE_TYPE_ERROR;
					}

					break;
				}

				case 1:
				{
					ret.imageOSType = IMAGE_TYPE_RT11;
					break;
				}

				default:
					ret.imageOSType = IMAGE_TYPE_ERROR;
			}
		}
	}

	ImgFile.Close();
	return ret;
}

//сравнение двух символов, без учёта регистра, русских букв не сравниваем, т.к. пока не нужно
//выход: TRUE - равны
//      FALSE - не равны
BOOL CBKParseImage::charcompare(BYTE a, BYTE b)
{
	if ('a' <= a && a <= 'z')
	{
		a -= ' '; //делаем из маленькой буквы большую
	}

	if ('a' <= b && b <= 'z')
	{
		b -= ' '; //делаем из маленькой буквы большую
	}

	if (a == b)
	{
		return TRUE;
	}

	return FALSE;
}

BOOL CBKParseImage::substrfind(BYTE *bufs, size_t len_bufs, BYTE *buff, size_t len_buff)
{
	size_t b_pos = 0; //позиция в буфере.
	size_t s_pos = 0; //позиция в строке, которую ищем
	size_t t_pos;
	BOOL bOk = FALSE;

	while (b_pos < len_bufs)
	{
		//пока есть где искать
		//ищем совпадение с первым символом
		if (charcompare(bufs[b_pos++], buff[s_pos]))
		{
			//нашли. теперь проверим на совпадения с остальными символами
			t_pos = b_pos;
			s_pos++; //первый символ совпал, начинаем проверять со второго
			bOk = TRUE; //флаг выхода из цикла поиска на совпадение строки

			while (s_pos < len_buff)
			{
				//пока строка не кончится
				if (!charcompare(buff[s_pos++], bufs[t_pos++]))
				{
					//если символ не совпал
					bOk = FALSE; //то не ОК
					break;
				}
			}

			b_pos = t_pos;
			s_pos = 0; //вернёмся на начало строки

			if (bOk)
			{
				//если таки вся строка совпала
				break; //незачем дальше искать
			}
		}
	}

	//на выходе отсюда bOk == TRUE если строка нашлась, иначе - FALSE
	return bOk;
}

/*
Проверим, а не rt11 ли это старым методом. Поскольку непонятно как узнать, что это у нас УКНЦшный АДОС.
выход:
1 - rt11
0 - не rt11
-1 - ошибка чтения файла
*/
int CBKParseImage::IsRT11_old(CFile *f, ULONGLONG nBaseOffset, BYTE *buf)
{
	int nRet = 0;

	//перемещаемся к нулевому сектору
	if (nBaseOffset + 0 == f->Seek(nBaseOffset + 0, CFile::begin))
	{
		if (f->Read(buf, 512) < 512)
		{
			return -1;
		}
	}
	else
	{
		return -1;
	}

	//надо найти строку strID_RT11 где-то в нулевом секторе.
	size_t s_len = strlen(strID_RT11);
	size_t b_len = 512 - s_len;
	BOOL bOk = substrfind(buf, b_len, (BYTE *)strID_RT11, s_len);

	if (bOk)
	{
		nRet = 1;
	}
	else
	{
		//если не нашли строку первого вида, поищем второго
		s_len = strlen(strID_RT11Q);
		b_len = 512 - s_len;
		bOk = substrfind(buf, b_len, (BYTE *) strID_RT11Q, s_len);

		if (bOk)
		{
			nRet = 1;
		}
	}

	return nRet;
}

/*
Проверим, а не rt11 ли это. выход:
1 - rt11
0 - не rt11
-1 - ошибка чтения файла
*/
int CBKParseImage::IsRT11(CFile *f, ULONGLONG nBaseOffset, BYTE *buf)
{
	int nRet = 0;
	WORD *pwBuf = reinterpret_cast<WORD *>(buf);
	RT11SegmentHdr CurrentSegmentHeader;
	//проверку будем осуществлять методом эмуляции чтения каталога.
	WORD nImgSize = LOWORD((int)f->GetLength() / 512); //размер образа в блоках.

	//перемещаемся к первому сектору
	if (nBaseOffset + 512 == f->Seek(nBaseOffset + 512, CFile::begin))
	{
		if (f->Read(buf, 512) < 512)
		{
			return -1;
		}
	}
	else
	{
		return -1;
	}

	WORD nBeginBlock = pwBuf[0724 / 2]; //получим блок, с которого начинается сектор.
	WORD nCurrentSegment = 1; //номер текущего сегмента
	WORD nTotalSegments = 0;    //всего возможных сегментов в каталоге
	WORD nUsedSegments = -1;    //число занятых сегментов, не должно совпадать с nCount
	WORD nAddWords = 0;
	WORD nCount = 0; //счётчик пройденных сегментов

	if (nBeginBlock < nImgSize)
	{
		while (nCurrentSegment > 0)
		{
			int offs = ((nCurrentSegment - 1) * 2 + nBeginBlock) * 512;

			if (nBaseOffset + offs == f->Seek(nBaseOffset + offs, CFile::begin)) //перемещаемся к началу текущего сегмента каталога
			{
				if (f->Read(&CurrentSegmentHeader, sizeof(RT11SegmentHdr)) != sizeof(RT11SegmentHdr))
				{
					return -1;
				}
			}
			else
			{
				return -1;
			}

			if (nTotalSegments == 0)
			{
				nTotalSegments = CurrentSegmentHeader.segments_num; //если в первый раз, запомним число сегментов.

				if (nTotalSegments > 31) //если число сегментов больше 31, то не RT-11
				{
					break;
				}

				nUsedSegments = CurrentSegmentHeader.used_segments;

				//если в первом сегменте счётчик использованных сегментов больше общего числа сегментов
				if (nUsedSegments > nTotalSegments)
				{
					break;
				}

				nAddWords = CurrentSegmentHeader.filerec_add;

				if (nAddWords > 252) //если число доп слов больше размера сегмента - то это не RT-11
				{
					//т.к. в сегменте обязательно должен быть маркер конца сегмента, то в нем должны умещаться как минимум 2 записи.
					//поэтому 72*7 делим пополам
					break;
				}
			}
			else
			{
				//если не в первый - сравним старое кол-во с новым, если не равно - значит не RT-11
				if (nTotalSegments != CurrentSegmentHeader.segments_num)
				{
					break;
				}

				if (nAddWords != CurrentSegmentHeader.filerec_add)
				{
					break;
				}
			}

			//если начальный блок в сегменте выходит за пределы образа - то это не RT-11
			if (CurrentSegmentHeader.file_block > nImgSize)
			{
				break;
			}

			nCurrentSegment = CurrentSegmentHeader.next_segment;

			if (nCurrentSegment > 31) //если номер следующего сегмента больше 31, то не RT-11
			{
				break;
			}

			if (nCurrentSegment > nTotalSegments) //если номер следующего сегмента больше общего ко-ва сегментов, то не RT-11
			{
				break;
			}

			nCount++; //чтобы не зациклиться, считаем пройденные сегменты

			if (nCount > nTotalSegments) //если их было больше, чем общее число сегментов - то это не RT-11
			{
				break;
			}
		}

		//когда выходим из цикла
		//если соблюдаются следующие условия, то очень вероятно, что это всё-таки RT-11
		// третье условие - избыточно
		if ((nCurrentSegment == 0) && (nCount == nUsedSegments) && (nCount <= nTotalSegments))
		{
			nRet = 1;
		}
	}

	return nRet;
}

/*
Проверим, а не ксидос ли это. выход:
1 - ксидос
0 - не ксидос
-1 - ошибка чтения
*/
int CBKParseImage::IsCSIDOS3(CFile *f, ULONGLONG nBaseOffset, BYTE *buf)
{
	WORD *pwBuf = reinterpret_cast<WORD *>(buf);
	int nRet = -1;

	//проверим, а не ксидос ли у нас
	if (nBaseOffset + 1024 == f->Seek(nBaseOffset + 1024, CFile::begin))
	{
		if (f->Read(buf, 512) < 512)
		{
			nRet = -1;
		}
		else
		{
			if (pwBuf[8 / 2] == 0123123 || pwBuf[8 / 2] == 0123124)
			{
				//если ксидос или особый ксидос
				if (pwBuf[4 / 2] == 0123123 && pwBuf[6 / 2] == 0123123) //если ксидос3
				{
					nRet = 1;
				}
				else
				{
					nRet = 0; //про ксидос других версий ничего неизвестно
				}
			}
			else
			{
				nRet = 0; //вообще не ксидос
			}
		}
	}

	return nRet;
}
