#include "StdAfx.h"
#include "BKDMkdos.h"

CBKDMkdos::CBKDMkdos(const PARSE_RESULT &image) : CBKDImage(image)
{
	m_nCatSize = 20 * 512; //всю нулевую дорожку будем считать каталогом. //*(WORD*)&m_mSector[FMT_MKDOS_FIRST_FILE_BLOCK] * 512;
	//хотя размер каталога в мкдосе почти 9 секторов, чуть-чуть до конца сектора не добивает.
	m_pCatTbl = new BYTE[m_nCatSize]; //необязательно выделять массив во время чтения каталога, размер-то его известен
	m_nMKCatSize = MKDOS_CAT_RECORD_SIZE;
}

CBKDMkdos::~CBKDMkdos(void)
{
	if (m_pCatTbl)
	{
		delete [] m_pCatTbl;
	}
}

BOOL CBKDMkdos::ReadCatalog()
{
	if (!CBKDImage::ReadCatalog())
	{
		return FALSE;
	}

	FileRecord curr_fr;

	if (m_pCatTbl == NULL)
	{
		m_nLastErrorNumber = IMAGE_ERROR_NOT_ENOUGHT_MEMORY;
		return FALSE;
	}

	memset(m_pCatTbl, 0, m_nCatSize);
	MKDosFileRecord *pMKCat = reinterpret_cast<MKDosFileRecord *>(m_pCatTbl + FMT_MKDOS_CAT_BEGIN);
	//читаем нулевую дорожку
	ULONGLONG ulls = m_sPImg.nBaseOffset + 0;

	if (ulls != m_ImgFile.Seek(ulls, CFile::begin))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		return FALSE;
	}

	if (m_nCatSize != m_ImgFile.Read(m_pCatTbl, m_nCatSize))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
		return FALSE;
	}

	int files_count = 0, recs_count = 0;
	int files_total = *(WORD *)&m_pCatTbl[FMT_MKDOS_CAT_RECORD_NUMBER]; //читаем общее кол-во файлов. (НЕ записей!)
	int data_begin = *(WORD *)&m_pCatTbl[FMT_MKDOS_FIRST_FILE_BLOCK] * 512; //смещение до данных
	m_sDiskCat.nTotalRecs = MKDOS_CAT_RECORD_SIZE; //это у нас объём каталога, из него надо будет вычесть общее количество записей
	int used_size = 0;

	for (int tc = 0; tc < MKDOS_CAT_RECORD_SIZE; tc++) //цикл по всему каталогу
	{
		if (files_count >= files_total) //файлы кончились, выходим
		{
			break;
		}

		//преобразуем запись и поместим в массив
		if (pMKCat[tc].status == 0 && pMKCat[tc].name[0] == 0)
		{
			break; //теоретически это конец каталога
		}

		recs_count++;
		curr_fr.clear();

		if (pMKCat[tc].status == 0377)
		{
			curr_fr.nAttr |= FR_ATTR_DELETED;
			recs_count--; //не считается
		}
		else if (pMKCat[tc].status == 0200)
		{
			curr_fr.nAttr |= FR_ATTR_BAD;

			if (pMKCat[tc].name[0] == 0)
			{
				pMKCat[tc].name[0] = 'B';
				pMKCat[tc].name[1] = 'A';
				pMKCat[tc].name[2] = 'D';
			}

			//это наверное тоже не считается, но проверить не на чём
		}
		else
		{
			files_count++;
		}

		if (pMKCat[tc].name[0] == 0177)
		{
			//если директория
			curr_fr.nAttr |= FR_ATTR_DIR;
			curr_fr.nRecType = RECORD_TYPE_DIR;
			curr_fr.strName = BKToUNICODE((BYTE *)pMKCat[tc].name + 1, 13, koi8tbl10).Trim();
			curr_fr.nDirBelong = pMKCat[tc].dir_num;
			curr_fr.nDirNum = pMKCat[tc].status;
			curr_fr.nBlkSize = 0;
		}
		else
		{
			//если файл
			CString name = BKToUNICODE((BYTE *)pMKCat[tc].name, 14, koi8tbl10).Trim();
			CString ext = _T("");
			int l = name.GetLength();
			int t = name.ReverseFind(_T('.'));

			if (t >= 0) //если расширение есть
			{
				ext = name.Right(l - t - 1);
				name = name.Left(t).Trim();
			}

			if (!ext.IsEmpty())
			{
				name += _T(".") + ext;
			}

			curr_fr.strName = name;
			curr_fr.nDirBelong = pMKCat[tc].dir_num;
			curr_fr.nDirNum = 0;

			if (curr_fr.nAttr & FR_ATTR_DELETED)
			{
				curr_fr.nDirBelong = 0; //а то удалённые файлы вообще не видны
				curr_fr.nRecType = RECORD_TYPE_FILE;
				curr_fr.nBlkSize = pMKCat[tc].len_blk;
			}
			else
			{
				if (pMKCat[tc].status == 2)
				{
					//лог. диск это просто ещё каталог и файлы.
					curr_fr.nAttr |= FR_ATTR_LOGDISK;
					curr_fr.nRecType = RECORD_TYPE_LOGDSK;
				}
				else
				{
					curr_fr.nRecType = RECORD_TYPE_FILE;
				}

				if (pMKCat[tc].status == 1)
				{
					curr_fr.nAttr |= FR_ATTR_PROTECTED;
				}

				used_size += pMKCat[tc].len_blk;
				curr_fr.nBlkSize = pMKCat[tc].len_blk;
			}
		}

		curr_fr.nAddress = pMKCat[tc].address;

		if (curr_fr.nAttr & FR_ATTR_LOGDISK) //если у нас логический диск, то размер надо считать по блокам.
		{
			curr_fr.nSize = pMKCat[tc].len_blk * 512;
		}
		else
		{
			/*  правильный расчёт длины файла.
			т.к. в curr_record.length хранится остаток длины по модулю 0200000, нам из длины в блоках надо узнать
			сколько частей по 0200000 т.е. сколько в блоках частей по 128 блоков.(128блоков == 65536.== 0200000)

			hw = (curr_record.len_blk >>7 ) << 16; //это старшее слово двойного слова
			hw = (curr_record.len_blk / 128) * 65536 = curr_record.len_blk * 512
			*/
			DWORD hw = (pMKCat[tc].len_blk << 9) & 0xffff0000; //это старшее слово двойного слова
			curr_fr.nSize = hw + pMKCat[tc].length; //это ст.слово + мл.слово
		}

		curr_fr.nStartBlock = pMKCat[tc].start_blok;
		m_sDiskCat.vecFC.push_back(curr_fr);
	}

	m_sDiskCat.nFreeRecs = m_sDiskCat.nTotalRecs - recs_count;
	m_sDiskCat.nTotalBlocks = *(WORD *)&m_pCatTbl[FMT_MKDOS_DISK_SIZE] - *(WORD *)&m_pCatTbl[FMT_MKDOS_FIRST_FILE_BLOCK];
	m_sDiskCat.nFreeBlocks = m_sDiskCat.nTotalBlocks - used_size;
	return TRUE;
}

FILESAVE_STATUS CBKDMkdos::AddFile(const CString &str, int nDirBelong, BOOL bAddAsBin)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;

	if (m_bFileROMode)
	{
		//Если образ открылся только для чтения,
		return FS_IMAGE_RO; //то записать в него мы ничего не сможем.
	}

	FILESAVE_STATUS bRet = FS_IO_ERROR;
	AnalyseFileStr sAFS;
	sAFS.bUseAsBin = bAddAsBin;
	int CatOffs = 0; //смещение в массиве каталога, до первой найденной свободной записи.
	MKDosFileRecord mFR;

	if (sAFS.file.Open(str, CFile::modeRead | CFile::shareDenyWrite))
	{
		if (!AnalyseImportFile(&sAFS))
		{
			return FS_FILE_OPENERR;
		}

		if (!sAFS.strExt.IsEmpty())
		{
			sAFS.strName += _T(".") + sAFS.strExt;
		}

		UNICODEtoBK(sAFS.strName, mFR.name, 14, TRUE);
		int nBlkLen = (sAFS.nLen) ? (((sAFS.nLen - 1) | 0777) + 1) : 0; //размер проги в блоках
		mFR.dir_num = nDirBelong; //номер текущего открытого подкаталога
		mFR.address = sAFS.nAddr; //возможно, если мы сохраняем бин файл, адрес будет браться оттуда
		mFR.len_blk = nBlkLen / 01000; //размер в блоках
		mFR.length = sAFS.nLen % 0200000; //остаток от длины по модулю 65535

		if (m_sDiskCat.nFreeBlocks * 01000 < nBlkLen)
		{
			sAFS.file.Close();
			return FS_DISK_FULL;
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR_FILE_CANNOT_OPEN;
		return FS_FILE_OPENERR;
	}

	memset(m_pCatTbl, 0, m_nCatSize);
	MKDosFileRecord *pMKCat = reinterpret_cast<MKDosFileRecord *>(m_pCatTbl + FMT_MKDOS_CAT_BEGIN);
	/*
	1. прочитать каталог.
	2. найти в каталоге свободное место
	3. если нашлось, убедиться, что на диске достаточно свободного места
	4. если всё нормально, сохранить файл
	5. создать запись о файле, в текущей директории
	6. поправить константы.
	7. сохранить каталог.
	*/
	BOOL bMode = FALSE; //что нашли, дырку или место в конце, т.к. разные способы обработки каталога
	//перемещаемся в начало образа
	ULONGLONG ulls = m_sPImg.nBaseOffset + 0;

	if (ulls == m_ImgFile.Seek(ulls, CFile::begin))
	{
		//прочтем каталог
		if (m_nCatSize == m_ImgFile.Read(m_pCatTbl, m_nCatSize))
		{
			//поищем, вдруг такое имя файла уже существует.
			int tc;
			BOOL bFound = FindRecord(pMKCat, mFR, FALSE, tc);

			if (bFound)
			{
				bRet = FS_FILE_EXIST;
			}
			else
			{
				//найдём свободное место в каталоге.
				bFound = FALSE;
				int nFirstFreeBlock = 0;

				for (tc = 0; tc < m_nMKCatSize; tc++)
				{
					if ((pMKCat[tc].status == 0377) && (pMKCat[tc].len_blk >= mFR.len_blk))
					{
						//если нашли дырку подходящего размера
						bMode = FALSE;
						CatOffs = tc;
						nFirstFreeBlock = pMKCat[tc].start_blok;
						bFound = TRUE;
						break;
					}

					if ((pMKCat[tc].status == 0) && ((pMKCat[tc].name[0] | pMKCat[tc].name[1]) == 0))
					{
						//если нашли свободную область
						bMode = TRUE;
						CatOffs = tc;
						nFirstFreeBlock = pMKCat[tc].start_blok;
						bFound = TRUE;
						break;
					}
				}

				if (bFound)
				{
					//можно сохранять
					mFR.start_blok = nFirstFreeBlock;

					//перемещаемся к месту
					if (m_sPImg.nBaseOffset + nFirstFreeBlock * 512 == m_ImgFile.Seek(m_sPImg.nBaseOffset + nFirstFreeBlock * 512, CFile::begin))
					{
						int nReaded;

						do
						{
							memset(m_mBlock, 0, COPY_BLOCK_SIZE);
							nReaded = sAFS.file.Read(m_mBlock, COPY_BLOCK_SIZE);
							//пишем с выравниванием по секторам.
							m_ImgFile.Write(m_mBlock, ((nReaded) ? (((nReaded - 1) | 0777) + 1) : 0));
							sAFS.nLen -= nReaded;
						}
						while (sAFS.nLen > 0);

						bRet = FS_NO_ERROR;
					}
				}
				else
				{
					bRet = FS_CAT_FULL;
				}
			}
		}
		else
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
	}

	sAFS.file.Close();

	if (bRet == FS_NO_ERROR)
	{
		//если ошибок не произошло, сохраним результаты
		*(WORD *)&m_pCatTbl[FMT_MKDOS_CAT_RECORD_NUMBER] += 1;
		*(WORD *)&m_pCatTbl[FMT_MKDOS_TOTAL_FILES_USED_BLOCKS] += mFR.len_blk;
		int nHoleSize;

		if (bMode)
		{
			//если нашли свободную область в конце каталога
			nHoleSize = pMKCat[CatOffs].len_blk;
			//сохраняем нашу запись
			pMKCat[CatOffs] = mFR;
			//сохраняем признак конца каталога
			MKDosFileRecord hole;
			hole.start_blok = mFR.start_blok + mFR.len_blk;
			hole.len_blk = nHoleSize - mFR.len_blk;
			//и запись с инфой о свободной области
			pMKCat[CatOffs + 1] = hole;
		}
		else
		{
			//если нашли дырку в каталоге, надо раздвинуть каталог, или не раздвигать, если размер дырки совпадает
			if ((nHoleSize = pMKCat[CatOffs].len_blk) > mFR.len_blk)
			{
				//раздвинем каталог
				int tc = CatOffs + 1;

				//найдём конец каталога
				while (tc < m_nMKCatSize)
				{
					if ((pMKCat[tc].status == 0) && ((pMKCat[tc].name[0] | pMKCat[tc].name[1]) == 0))
					{
						break;
					}

					tc++;
				}

				tc++;

				if (tc >= m_nMKCatSize) //если каталог раздвинуть нельзя
				{
					bRet = FS_CAT_FULL;
				}
				else
				{
					//раздвигаем
					while (tc > CatOffs)
					{
						pMKCat[tc] = pMKCat[tc - 1];
						tc--;
					}

					//сохраняем нашу запись
					pMKCat[CatOffs] = mFR;
					MKDosFileRecord hole;
					hole.status = 0377;
					hole.dir_num = 0377;
					hole.start_blok = mFR.start_blok + mFR.len_blk;
					hole.len_blk = nHoleSize - mFR.len_blk;
					strcpy_s((char *)hole.name, 14, "<HOLE>");
					hole.length = (hole.len_blk * 512) % 0200000;
					//и запись с инфой о дырке
					pMKCat[CatOffs + 1] = hole;
				}
			}
			else
			{
				//ничего раздвигать не надо, просто
				//сохраняем нашу запись
				pMKCat[CatOffs] = mFR;
			}
		}

		if (bRet == FS_NO_ERROR)
		{
			//наконец сохраняем каталог
			m_ImgFile.Seek(m_sPImg.nBaseOffset + 0, CFile::begin);
			m_ImgFile.Write(m_pCatTbl, m_nCatSize);
		}
	}

	return bRet;
}
/*
для остальных микродосных ос процедура такая же, только с вариациями на тему
разных форматов атрибутов и признака конца каталога.
...
Отнюдь не всё так просто, как хотелось бы. аодос - глючнейший формат.
*/

/*
поиск записи в каталоге.
вход:   pCat - указатель на массив каталога
aFR - искомая запись
bFull - FALSE - сравнивать только по имени, TRUE - ещё и по блоку/адресу/длине/каталогу

выход:  TRUE - нашлось, FALSE - не нашлось
tc - индекс в массиве pCat
*/
BOOL CBKDMkdos::FindRecord(MKDosFileRecord *pCat, MKDosFileRecord &mFR, BOOL bFull, int &tc)
{
	BOOL bFound = FALSE;

	for (tc = 0; tc < m_nMKCatSize; tc++)
	{
		if ((pCat[tc].status == 0) && ((pCat[tc].name[0] | pCat[tc].name[1]) == 0))
		{
			break; //дошли до конца каталога, незачем дальше циклиться
		}

		if (pCat[tc].status == 0377)
		{
			//если удалённый файл - дырка, то игнорируем
		}
		else if (!memcmp((char *)mFR.name, (char *)pCat[tc].name, 14))  //иначе проверим имя
		{
			if (bFull)
			{
				if ((pCat[tc].dir_num == mFR.dir_num) &&
				        (pCat[tc].start_blok == mFR.start_blok) &&
				        (pCat[tc].len_blk == mFR.len_blk) &&
				        (pCat[tc].address == mFR.address)
				   )
				{
					bFound = TRUE;
					break;
				}
			}
			else
			{
				bFound = TRUE;
				break;
			}
		}
	}

	return bFound;
}

BOOL CBKDMkdos::DeleteFile(FileRecord *sFile)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;
	BOOL bRet = FALSE;

	if (m_bFileROMode)
	{
		//Если образ открылся только для чтения,
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_WRITE_FILE;
		return bRet; //то записать в него мы ничего не сможем.
	}

	MKDosFileRecord mFR;
	//преобразование FileRecord в MKDosFileRecord
	mFR.status = 0;
	mFR.dir_num = sFile->nDirNum;
	UNICODEtoBK(sFile->strName, mFR.name, 14, TRUE);
	mFR.start_blok = sFile->nStartBlock;
	mFR.len_blk = sFile->nBlkSize;
	mFR.address = sFile->nAddress;
	mFR.length = sFile->nSize % 0200000;
	/*
	1. Прочитать каталог
	2. Найти запись в каталоге
	3. Пометить запись как удалённую
	4. Исправить параметры
	5. Сохранить каталог
	*/
	//перемещаемся в начало образа
	ULONGLONG ulls = m_sPImg.nBaseOffset + 0;

	if (ulls == m_ImgFile.Seek(ulls, CFile::begin))
	{
		//прочтем каталог
		if (m_nCatSize == m_ImgFile.Read(m_pCatTbl, m_nCatSize))
		{
			MKDosFileRecord *pMKCat = reinterpret_cast<MKDosFileRecord *>(m_pCatTbl + FMT_MKDOS_CAT_BEGIN);
			int tc;

			if (FindRecord(pMKCat, mFR, TRUE, tc))
			{
				pMKCat[tc].status = 0377;
				*(WORD *)&m_pCatTbl[FMT_MKDOS_CAT_RECORD_NUMBER] -= 1;
				*(WORD *)&m_pCatTbl[FMT_MKDOS_TOTAL_FILES_USED_BLOCKS] -= pMKCat[tc].len_blk;
				//наконец сохраняем каталог
				m_ImgFile.Seek(ulls, CFile::begin); //проверки не нужны, мы ведь смогли прочитать, сможем и записать
				m_ImgFile.Write(m_pCatTbl, m_nCatSize);
				bRet = TRUE;
			}
		}
		else
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
	}

	return bRet;
}

