#include "StdAfx.h"
#include "BKDAodos.h"

CBKDAodos::CBKDAodos(const PARSE_RESULT &image) : CBKDImage(image)
{
}

CBKDAodos::~CBKDAodos(void)
{
}

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

	AodosFileRecord curr_record;
	FileRecord curr_fr;

	//читаем нулевой сектор
	if (m_sPImg.nBaseOffset + 0 != m_ImgFile.Seek(m_sPImg.nBaseOffset + 0, CFile::begin))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		return FALSE;
	}

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

	int recs_count = 0;
	int files_total = *(WORD *)&m_mSector[FMT_AODOS_CAT_RECORD_NUMBER]; //читаем общее кол-во файлов
	m_sDiskCat.nTotalRecs = (012000 - FMT_AODOS_CAT_BEGIN) / sizeof(AodosFileRecord); //это у нас объём каталога, из него надо будет вычесть общее количество записей
	int used_size = 0;

	if (m_sPImg.nBaseOffset + FMT_AODOS_CAT_BEGIN != m_ImgFile.Seek(m_sPImg.nBaseOffset + FMT_AODOS_CAT_BEGIN, CFile::begin)) //перемещаемся к началу каталога.
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		return FALSE;
	}

	while (--files_total >= 0)
	{
		if (m_ImgFile.Read(&curr_record, sizeof(AodosFileRecord)) < sizeof(AodosFileRecord))
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
			return FALSE; //если даже каталог не прочёлся, а файл уже кончился, то образ повреждён,
			//и делать с ним нечего.
		}

		recs_count++;

		if ((curr_record.status1 | curr_record.status2 | curr_record.name[0] | curr_record.name[1]) == 0)
		{
			break;    //конец каталога для аодос 1.77
		}

		//успешно прочли запись, теперь преобразуем её и поместим в массив
		curr_fr.clear();

		if (curr_record.status2 & 0200)
		{
			curr_fr.nAttr |= FR_ATTR_DELETED;
			recs_count--;
		}
		else if (curr_record.status1 & 0200)
		{
			curr_fr.nAttr |= FR_ATTR_BAD;

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

		CString name = BKToUNICODE((BYTE *)curr_record.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.nDirNum = curr_record.status2 & 0x7f;

		if ((curr_record.len_blk == 0) && !(curr_fr.nAttr & FR_ATTR_DELETED))
		{
			//если директория
			curr_fr.nAttr |= FR_ATTR_DIR;
			curr_fr.nRecType = RECORD_TYPE_DIR;
			curr_fr.nDirDir = curr_record.status1 & 0x7f;
			curr_fr.nAddress = 0; //если копировать curr_record.address, то директории начинают притворяться линками,
			//а в микродосе их по известным мне данным, не должно быть
			curr_fr.nBlkSize = 0;
		}
		else
		{
			//если файл
			curr_fr.nRecType = RECORD_TYPE_FILE;
			curr_fr.nDirDir = 0;
			curr_fr.nAddress = curr_record.address;

			if (curr_fr.nAttr & FR_ATTR_DELETED)
			{
				curr_fr.nDirNum = 0; //а то удалённые файлы вообще не видны
				//зато теперь стали видны и записи - дырки.
			}
			else
			{
				if (curr_record.status1 & 1)
				{
					curr_fr.nAttr |= FR_ATTR_PROTECTED;
				}

				if (curr_record.status1 & 2)
				{
					curr_fr.nAttr |= FR_ATTR_HIDDEN;
				}

				used_size += curr_record.len_blk;
				curr_fr.nBlkSize = curr_record.len_blk;
			}
		}

		/*  правильный расчёт длины файла.
		т.к. в 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
		*/
		/*
		на случай, если в аодосе действительно по модулю 0100000 а не 0200000, то делать надо так
		0100000 == 32768. == 64 блока.
		невозможно проверить, т.к. ни в одном образе не встретилось файла больше 64кб.
		hw = (curr_record.len_blk / 64) * 32768 = curr_record.len_blk * 512
		hw = (curr_record.len_blk >> 6) << 15 = curr_record.len_blk * 512
		получается почти так же как и выше. одна лишь разница - не 16 старших битов, а 17
		DWORD hw = (curr_record.len_blk  << 9) & 0xffff8000; //это старшее слово двойного слова
		*/
		DWORD hw = (curr_record.len_blk << 9) & 0xffff0000; //это старшее слово двойного слова
		curr_fr.nSize = hw + curr_record.length; //это ст.слово + мл.слово
		curr_fr.nStartSector = curr_record.start_blok;
		m_sDiskCat.vecFC.push_back(curr_fr);
	}

	m_sDiskCat.nFreeRecs = m_sDiskCat.nTotalRecs - recs_count;
	//некоторые аодосы не имеют размера диска в блоках в ячейке 0466
	int imgSize = *(WORD *)&m_mSector[FMT_AODOS_DISK_SIZE];

	if ((imgSize == 0) || (imgSize > 32767)) //т.е. если там число положительное, даже если оно там случайно, то оно будет считаться размером диска в блоках. и ниипет.
	{
		/*тут такая проблема.
		800кб образ может быть меньше 800 кб, т.к. программа, делатель образов,
		могла из-за оптимизации не читать пустые дорожки в конце диска.
		А с другой стороны, в МКДОС, внутри логического диска может быть любая ОС, в том числе и АОДОС,
		и размер диска может быть меньше 800кб.
		*/
		imgSize = max(1600, (int)m_sPImg.imageSize / 512);
	}

	m_sDiskCat.nTotalBlocks = imgSize - 20;
	m_sDiskCat.nFreeBlocks = m_sDiskCat.nTotalBlocks - used_size;
	return TRUE;
}

FILESAVE_STATUS CBKDAodos::AddFile(const CString &str, int nDirNum, 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;
	UINT CatOffs = 0; //смещение в массиве каталога, до первой найденной свободной записи.
	UINT nCatSize = 012 * 512; //размер каталога - строго 10. секторов. вторые 10 секторов - копия, данные начинаются с 20. сектора
	AodosFileRecord 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.status2 = nDirNum & 0x7f; //номер текущего открытого подкаталога
		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;
	}

	BYTE *pCat = new BYTE[nCatSize];
	memset(pCat, 0, nCatSize);
	//этот же каталог, для удобства будем рассматривать в записях
	AodosFileRecord *pAOCat = reinterpret_cast<AodosFileRecord *>(pCat + FMT_AODOS_CAT_BEGIN);
	UINT nAOCatSize = (nCatSize - FMT_AODOS_CAT_BEGIN) / sizeof(AodosFileRecord);
	/*
	1. прочитать каталог.
	2. найти в каталоге свободное место
	3. если нашлось, убедиться, что на диске достаточно свободного места
	4. если всё нормально, сохранить файл
	5. создать запись о файле, в текущей директории
	6. поправить константы.
	7. сохранить каталог.
	*/
	UINT nRecs;

	//перемещаемся в начало образа
	if (m_sPImg.nBaseOffset + 0 != m_ImgFile.Seek(m_sPImg.nBaseOffset + 0, CFile::begin))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		return bRet;
	}

	//прочтем каталог
	if (nCatSize == m_ImgFile.Read(pCat, nCatSize))
	{
		nRecs = *(WORD *)&pCat[FMT_AODOS_CAT_RECORD_NUMBER]; // кол-во записей в каталоге, + 1 - для аодос 2.10 признак конца каталога
		/*
		Весь алгоритм строится на предположении о том, что в FMT_AODOS_CAT_RECORD_NUMBER хранится
		общее количество всех записей в каталоге, включая дырки и конечную запись.
		Почти так и есть. только конечная запись в число не входит, поэтому проверка с nRecs на == тоже делается.
		*/
		//поищем, вдруг такое имя файла уже существует.
		UINT tc = 0;
		BOOL bFound = FALSE;

		while ((tc <= nRecs) && (tc < nAOCatSize))
		{
			if ((pAOCat[tc].status1 | pAOCat[tc].status2 | pAOCat[tc].name[0] | pAOCat[tc].name[1]) == 0)
			{
				break; //конец каталога для аодос 1.7 (но не факт.)
			}

			if (pAOCat[tc].status2 & 0200)
			{
				//удалённый файл проигнорируем.
			}
			else if (!strncmp((char *)mFR.name, (char *)pAOCat[tc].name, 14))
			{
				bFound = TRUE;
				break;
			}

			tc++;
		}

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

			while ((tc <= nRecs) && (tc < nAOCatSize))
			{
				if (pAOCat[tc].status2 & 0200)
				{
					if (pAOCat[tc].len_blk >= mFR.len_blk)
					{
						//если нашли дырку подходящего размера
						bFound = TRUE;
						CatOffs = tc;
						nFirstFreeBlock = pAOCat[tc].start_blok;
						break;
					}
				}

				tc++;
			}

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

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

				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;
	}

	sAFS.file.Close();

	if (bRet == FS_NO_ERROR)
	{
		//если ошибок не произошло, сохраним результаты
		UINT nHoleSize;

		if ((nHoleSize = pAOCat[CatOffs].len_blk) > mFR.len_blk)
		{
			//раздвинем каталог
			*(WORD *)&pCat[FMT_AODOS_CAT_RECORD_NUMBER] += 1;
			//найдём конец каталога
			UINT tc = 0;

			if (CatOffs == nRecs)
			{
				tc = CatOffs + 1;
			}
			else
			{
				while ((tc <= nRecs) && (tc < nAOCatSize))
				{
					if ((pAOCat[tc].status1 | pAOCat[tc].status2 | pAOCat[tc].name[0] | pAOCat[tc].name[1]) == 0)
					{
						break;
					}

					tc++;
				}
			}

			if (tc >= nAOCatSize) //если каталог раздвинуть нельзя
			{
				bRet = FS_CAT_FULL;
			}
			else
			{
				//раздвигаем
				int cnt = 0; //признак

				while (tc > CatOffs)
				{
					pAOCat[tc] = pAOCat[tc - 1];
					tc--;
					cnt++;
				}

				//сохраняем нашу запись
				pAOCat[CatOffs] = mFR;
				AodosFileRecord hole;
				hole.status1 = 0377;
				hole.status2 = 0377;
				strcpy_s((char *)hole.name, 14, "<UNUSED>");
				hole.start_blok = mFR.start_blok + mFR.len_blk;
				hole.len_blk = nHoleSize - mFR.len_blk;
				hole.length = (hole.len_blk * 512) % 0200000;
				//и запись с инфой о дырке
				pAOCat[CatOffs + 1] = hole;

				//если мы в каталоге раздвигая, раздвинули только на одну запись - то мы оказались в конце каталога
				//при этом надо поправить начало первого свободного блока в ячейке 032
				if (cnt == 1)
				{
					*(WORD *)&pCat[FMT_AODOS_FIRST_FREE_BLOCK] = hole.start_blok;
				}
			}
		}
		else
		{
			//ничего раздвигать не надо, просто
			//сохраняем нашу запись
			pAOCat[CatOffs] = mFR;
		}

		if (bRet == FS_NO_ERROR)
		{
			//наконец сохраняем каталог
			m_ImgFile.Seek(m_sPImg.nBaseOffset + 0, CFile::begin);
			m_ImgFile.Write(pCat, nCatSize);
			m_ImgFile.Write(pCat, nCatSize); //и ещё раз, копию.
		}
	}

	delete [] pCat;
	return bRet;
}
