// LoadImgDlg.cpp: файл реализации
//

#include "stdafx.h"
#include "BK.h"
#include "LoadImgDlg.h"


// CLoadImgDlg

IMPLEMENT_DYNAMIC(CLoadImgDlg, CFileDialog)

CLoadImgDlg::CLoadImgDlg(BOOL bOpenFileDialog, LPCTSTR lpszDefExt, LPCTSTR lpszFileName,
                         DWORD dwFlags, LPCTSTR lpszFilter, CWnd *pParentWnd, DWORD dwSize, BOOL bVistaStyle) :
	CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle)
{
	m_ofn.hwndOwner = pParentWnd->GetSafeHwnd();
	m_ofn.Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;
	m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_LOAD_IMAGE);
	m_ofn.lpfnHook = OFNHookProc;
	m_ofn.lCustData = reinterpret_cast<LPARAM>(this);
}

CLoadImgDlg::~CLoadImgDlg()
{
}


BEGIN_MESSAGE_MAP(CLoadImgDlg, CFileDialog)
END_MESSAGE_MAP()



// обработчики сообщений CLoadImgDlg
UINT_PTR CALLBACK CLoadImgDlg::OFNHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	static CRect rectHD;
	CLoadImgDlg *obj = reinterpret_cast<CLoadImgDlg *>(lParam);
	//!!! под Win8 хук работает, но параметр ему не передаётся, поэтому обойдёмся статическими объектами

	switch (uiMsg)
	{
		case WM_INITDIALOG:
		{
			::GetWindowRect(hdlg, &rectHD);
			break;
		}

		case WM_CHILDACTIVATE:
		{
			BOOL bres;
			HWND hParent = ::GetParent(hdlg);
			CRect rectFD;
			bres = ::GetWindowRect(hParent, &rectFD);
			CRect rectHD1;
			bres = ::GetWindowRect(hdlg, &rectHD1);
			//увеличим окно в ширину
			bres = ::MoveWindow(hParent, rectFD.left, rectFD.top, rectFD.Width() + rectHD.Width(), rectFD.Height() - rectHD.Height(), FALSE);
			//переместим превьюшку на своё новое место
			bres = ::MoveWindow(hdlg, rectFD.Width(), -355, rectHD.Width(), rectHD1.Height(), FALSE);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR pNm = reinterpret_cast<LPNMHDR >(lParam);
			LPOFNOTIFY pOFN = reinterpret_cast<LPOFNOTIFY >(wParam);

			if (pNm->code == CDN_SELCHANGE)
			{
				CString strPath;
				HWND hParent = ::GetParent(hdlg);
				::SendMessage(hParent, CDM_GETFILEPATH, 1024, (LPARAM)strPath.GetBufferSetLength(1024));
				strPath.ReleaseBuffer();
				CLoadImgDlg::DrawPreview(hdlg, strPath);
				return 0;
			}

			break;
		}
	}

	return 0;
}

void CLoadImgDlg::DrawPreview(HWND hdlg, const CString &strPath)
{
	CWnd *pDlg = CWnd::FromHandle(hdlg);
	CString strFormatName, strSystemFlag;
	GetImgFormat(strPath, strFormatName, strSystemFlag);
	pDlg->GetDlgItem(IDC_LI_SYSTEM)->SetWindowText(strFormatName);
	pDlg->GetDlgItem(IDC_LI_BOOTABLE)->SetWindowText(strSystemFlag);
}


void CLoadImgDlg::GetImgFormat(const CString &strImageName, CString &strFormatName, CString &strSystemFlag)
{
	PARSE_RESULT res = ParseImage(strImageName);
	strSystemFlag = _T("");

	if (res.bImageBootable)
	{
		strSystemFlag.LoadString(IDS_DISK_SYSTEM);
	}

	switch (res.imageOSType)
	{
		case IMAGE_TYPE_ERROR:
			strFormatName.LoadString(IDS_DISK_FORMAT_ERROR);
			break;

		case IMAGE_TYPE_ANDOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_ANDOS);
			break;

		case IMAGE_TYPE_MKDOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_MKDOS);
			break;

		case IMAGE_TYPE_AODOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_AODOS);
			break;

		case IMAGE_TYPE_NORD:
			strFormatName.LoadString(IDS_DISK_FORMAT_NORD);
			break;

		case IMAGE_TYPE_MIKRODOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_MICRODOS);
			break;

		case IMAGE_TYPE_CSIDOS3:
			strFormatName.LoadString(IDS_DISK_FORMAT_CSIDOS);
			break;

		case IMAGE_TYPE_RT11:
			strFormatName.LoadString(IDS_DISK_FORMAT_RT11);
			break;

		case IMAGE_TYPE_NCDOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_NCDOS);
			break;

		case IMAGE_TYPE_DXDOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_DXDOS);
			break;

		case IMAGE_TYPE_MSDOS:
			strFormatName.LoadString(IDS_DISK_FORMAT_MSDOS);
			break;

		default:
			strFormatName.LoadString(IDS_DISK_FORMAT_UNKNOWN);
	}
}

const char *CLoadImgDlg::strID_Andos = "ANDOS ";
const char *CLoadImgDlg::strID_DXDOS = "DX-DOS";
const char *CLoadImgDlg::strID_FAT12 = "FAT12   ";
const char *CLoadImgDlg::strID_Aodos = " AO-DOS ";
const char *CLoadImgDlg::strID_Nord  = "NORD ";

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

	if (!ImgFile.Open(fname, CFile::modeRead | CFile::shareDenyNone))
	{
		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[2 / 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(&ImgFile, pSector);

			if (t == 1)
			{
				ret.imageOSType = IMAGE_TYPE_RT11;
			}
			else if (t == 0)
			{
				//нет, не рт11
				//проверим, а не ксидос ли у нас
				t = IsCSIDOS3(&ImgFile, pSector);

				if (t == 1)
				{
					ret.imageOSType = IMAGE_TYPE_CSIDOS3;
				}
				else if (t == 0)
				{
					ret.imageOSType = IMAGE_TYPE_UNKNOWN;
				}
				else
				{
					ret.imageOSType = IMAGE_TYPE_ERROR;
				}
			}
			else
			{
				ret.imageOSType = IMAGE_TYPE_ERROR;
			}
		}
	}

	ImgFile.Close();
	return ret;
}


/*
Проверим, а не rt11 ли это. выход:
1 - rt11
0 - не rt11
-1 - ошибка чтения файла
*/
int CLoadImgDlg::IsRT11(CFile *f, BYTE *buf)
{
	ULONGLONG nBaseOffset = 0;
	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 CLoadImgDlg::IsCSIDOS3(CFile *f, BYTE *buf)
{
	int ret = -1;

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

	return ret;
}

