#include "StdAfx.h"
#include "BKDNord.h"

CBKDNord::CBKDNord(const PARSE_RESULT &image) : CBKDImage(image)
{
	m_nVersion = 0;
}

CBKDNord::~CBKDNord()
{
}

bool CBKDNord::ReadCatalog()
{
	if (!CBKDImage::ReadCatalog())
	{
		return false;
	}

	NordFileRecord curr_record;
	FileRecord curr_fr;

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

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

	//пробуем определить версию. если по смещениям 012 и 014 будут цифры '3' и '5'
	//то будем считать, что это норд 3.x, иначе - 2.х
	m_nVersion = (m_mSector[012] == '3') ? 30 : 20;
	int files_count = 0, recs_count = 0;
	int files_total = *(uint16_t *)&m_mSector[FMT_NORD_CAT_RECORD_NUMBER]; //читаем общее кол-во записей.
	int catalog_pos = *(uint16_t *)&m_mSector[FMT_NORD_CAT_NUMBER]; //читаем кол-во и состояние копий каталогов на диске
	m_sDiskCat.nTotalRecs = (5120 - FMT_NORD_CAT_BEGIN) / sizeof(NordFileRecord); //это у нас объём каталога, из него надо будет вычесть общее количество записей
	int used_size = 0;
	int disk_size = *(uint16_t *)&m_mSector[FMT_NORD_DISK_SIZE];

	if ((disk_size == 0) || (disk_size > 32767))
	{
		//если указан какой-то неправильный размер, то размер берём из расчёта типа диска
		switch (*(uint16_t *)&m_mSector[FMT_NORD_DISK_TYPE])
		{
			case 0:
				disk_size = 80 * 2 * 10; //0 - 80 дор. 2 стороны
				break;

			case 1:
				disk_size = 40 * 2 * 10; //1 - 40 дор. 2 стороны
				break;

			case 2:
				disk_size = 80 * 1 * 10; //2 - 80 дор. 1 сторона
				break;

			case 3:
				disk_size = 40 * 1 * 10; //3 - 40 дор. 1 сторона
				break;
		}
	}

	if (*(uint16_t *)&m_mSector[FMT_NORD_BEGIN_BLOCK])
	{
		//если у нас логический диск, то берём размер логического диска
		disk_size = int(m_sParseImgResult.nImageSize) / 512;
	}
	else
	{
		//если у нас образ диска. то вычисляем посложнее
		disk_size = max(disk_size, int(m_sParseImgResult.nImageSize) / 512); //норд 3.5 может быть больше 800кб, сильно больше.
	}

	int cpos = FMT_NORD_CAT_BEGIN;

	switch (catalog_pos)
	{
		case 0:
		case 1:
			disk_size -= (m_nVersion == 30) ? 2 * 10 : 10; //0,1 - одна копия каталога, начальный сектор 0
			break;

		case 2: //2 - одна копия, начальный сектор 012
			//это означает что на диске есть только вторая копия каталога, все остальные числа
			//означают, что есть как минимум одна копия, и она в начале диска
			cpos += 012 * 512; //break не нужен

		case 3: //3 - две копии. 0 и 012 секторы
			disk_size -= 2 * 10; //т.е. по любому от свободного размера диска надо вычесть размер обеих копий каталога.
			break;
	}

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

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

		//успешно прочли запись, теперь преобразуем её и поместим в массив
		//для норд3.5 надо проверку на выход делать как в мкдосе, тут возможен глюк.
		// конец каталога необязательно 0, там может быть мусор, в этом случае будет мусор и прочитан.
		if (curr_record.status == 0 && curr_record.name[0] == 0)
		{
			break; //теоретически это конец каталога
		}

		//вот ещё одна проверка на конец каталога.
		if (curr_record.status == 0377 && curr_record.len_blk == 0177777 && curr_record.address == 0177777 && curr_record.length == 0177777)
		{
			break; //теоретически это конец каталога
		}

		//вводим дополнительную проверку на выход, чтобы минимизировать захват мусора
		if (m_FloppyImgFile.GetPosition() >= (m_sParseImgResult.nBaseOffset + cpos + 0xf00))
		{
			break;
		}

		recs_count++;
		curr_fr.clear();

		if (curr_record.name[0] == 0177)
		{
			//если директория
			curr_fr.nAttr |= FR_ATTR_DIR;
			curr_fr.nRecType = RECORD_TYPE_DIR;
			curr_fr.strName = BKToUNICODE((uint8_t *)curr_record.name + 1, 13, koi8tbl10).Trim();
			curr_fr.nDirBelong = curr_record.dir_num;
			curr_fr.nDirNum = curr_record.status;
			curr_fr.nAddress = 0; //curr_record.address; //тут надо 0, а то все директории считаются линками,
			//лучше уж случайный линк посчитать директорией, чем наоборот.
			curr_fr.nBlkSize = 0;
		}
		else
		{
			//если файл
			if (curr_record.status & 0200)
			{
				curr_fr.nAttr |= FR_ATTR_DELETED;

				//в норд 3.5 удалённые не считаются
				if (m_nVersion == 30)
				{
					files_total++;
				}

				//а в старых версиях - считаются, поэтому, есть шанс захватить мусор,
				//но лучше так, чем недобрать файлов из-за таких изменений
				recs_count--;
			}

			CString name = BKToUNICODE((uint8_t *)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.nDirBelong = curr_record.dir_num;
			curr_fr.nDirNum = 0;
			curr_fr.nAddress = curr_record.address;

			if (curr_fr.nAttr & FR_ATTR_DELETED)
			{
				curr_fr.nRecType = RECORD_TYPE_FILE;
				curr_fr.nDirBelong = 1; //а то удалённые файлы вообще не видны,
				//а так они будут находиться в специальной директории DEL
				//ну и дырки так же там
			}
			else
			{
				if (curr_record.status == 2)
				{
					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';
					}
				}

				if (curr_record.status & 0115)
				{
					curr_fr.nAttr |= FR_ATTR_PROTECTED;
				}

				if ((curr_record.name[1] == ':') && (curr_record.name[0] >= 'A' && curr_record.name[0] <= 'Z'))
				{
					curr_fr.nAttr |= FR_ATTR_LOGDISK;
					curr_fr.nRecType = RECORD_TYPE_LOGDSK;
				}
				else
				{
					curr_fr.nRecType = RECORD_TYPE_FILE;
				}

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

		if (curr_fr.nAttr & FR_ATTR_LOGDISK) //если у нас логический диск, то размер надо считать по блокам.
		{
			curr_fr.nSize = curr_record.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
			*/
			uint32_t hw = (curr_record.len_blk << 9) & 0xffff0000; //это старшее слово двойного слова
			curr_fr.nSize = hw + curr_record.length; //это ст.слово + мл.слово
		}

		curr_fr.nStartBlock = curr_record.start_blok;
		m_sDiskCat.vecFC.push_back(curr_fr);
	}

	m_sDiskCat.nFreeRecs = m_sDiskCat.nTotalRecs - recs_count;
	m_sDiskCat.nTotalBlocks = disk_size;
	m_sDiskCat.nFreeBlocks = disk_size - used_size;
	return true;
}

FILESAVE_STATUS CBKDNord::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;
	UINT CatOffs = 0; //смещение в массиве каталога, до первой найденной свободной записи.
	UINT nCatSize = 012 * 512;  //размер каталога - строго 10. секторов. вторые 10 секторов - копия, данные начинаются с 20. сектора
	//кстати вторая копия может и отсутствовать, а может отсутствовать первая копия
	NordFileRecord 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;
	}

	auto *pCat = new uint8_t[nCatSize];
	memset(pCat, 0, nCatSize);
	auto pNOCat = reinterpret_cast<NordFileRecord *>(pCat + FMT_NORD_CAT_BEGIN);
	UINT nNOCatSize = (nCatSize - FMT_NORD_CAT_BEGIN) / sizeof(NordFileRecord);

	/*
	1. прочитать каталог.
	2. найти в каталоге свободное место
	3. если нашлось, убедиться, что на диске достаточно свободного места
	4. если всё нормально, сохранить файл
	5. создать запись о файле, в текущей директории
	6. поправить константы.
	7. сохранить каталог.
	*/
	//если есть только вторая копия, переместимся к ней
	if (*(uint16_t *)&pCat[FMT_NORD_CAT_NUMBER] == 2)
	{
		if (m_sParseImgResult.nBaseOffset + nCatSize != m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + nCatSize, CFile::begin))
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
			return bRet;
		}
	}
	else
	{
		//перемещаемся в начало образа
		if (m_sParseImgResult.nBaseOffset + 0 != m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + 0, CFile::begin))
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
			return bRet;
		}
	}

	UINT nRecs;

	//прочтём каталог
	if (nCatSize == m_FloppyImgFile.Read(pCat, nCatSize))
	{
		nRecs = *(uint16_t *)&pCat[FMT_NORD_CAT_RECORD_NUMBER]; // кол-во записей в каталоге. (либо только записей, не включая удалённые)
		//поищем, вдруг такое имя файла уже существует.
		bool bFound = false;
		UINT tc = 0;
		UINT ntRecs = nRecs; //для норд 3 записи считаются не как для норд 2, поэтому такой изврат

		while ((tc <= ntRecs) && (tc < nNOCatSize))
		{
			if (pNOCat[tc].status & 0200) //если наткнулись на удалённую запись
			{
				if (m_nVersion == 30) //если норд 3,
				{
					//то надо её засчитать как запись.
					ntRecs++;
				}
			}
			else if (!strncmp((char *)mFR.name, (char *)pNOCat[tc].name, 14))
			{
				bFound = true;
				break;
			}

			tc++;
		}

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

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

					if (m_nVersion == 30) //если норд 3,
					{
						//то надо её засчитать как запись.
						ntRecs++;
					}
				}

				tc++;
			}

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

				//перемещаемся к месту
				if (m_sParseImgResult.nBaseOffset + nFirstFreeBlock * 512 == m_FloppyImgFile.Seek(m_sParseImgResult.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_FloppyImgFile.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 = pNOCat[CatOffs].len_blk) > mFR.len_blk)
		{
			//раздвинем каталог
			*(uint16_t *)&pCat[FMT_NORD_CAT_RECORD_NUMBER] += 1;
			//найдём конец каталога
			UINT tc = 0;

			if (CatOffs == nRecs)
			{
				tc = CatOffs + 1;
			}
			else
			{
				UINT ntRecs = nRecs;

				while ((tc <= ntRecs) && (tc < nNOCatSize))
				{
					if (pNOCat[tc].status & 0200)
					{
						if (m_nVersion == 30) //если норд 3,
						{
							//то надо её засчитать как запись.
							ntRecs++;
						}
					}

					tc++;
				}
			}

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

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

				//сохраняем нашу запись
				pNOCat[CatOffs] = mFR;
				NordFileRecord hole;
				hole.status = 0377;
				hole.dir_num = 0377;
				memset(hole.name, 0377, 14);
				hole.start_blok = mFR.start_blok + mFR.len_blk;
				hole.len_blk = nHoleSize - mFR.len_blk;
				hole.length = (hole.len_blk * 01000) % 0200000;
				//и запись с инфой о дырке
				pNOCat[CatOffs + 1] = hole;

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

		if (bRet == FS_NO_ERROR)
		{
			//наконец сохраняем каталог
			int m = *(uint16_t *)&pCat[FMT_NORD_CAT_NUMBER];

			//если есть первая копия, сохраняем её
			if ((m & 1) || (m == 0))
			{
				m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + 0, CFile::begin);
				m_FloppyImgFile.Write(pCat, nCatSize);
			}

			//если есть вторая копия, сохраняем её
			if (m & 2)
			{
				m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + nCatSize, CFile::begin);
				m_FloppyImgFile.Write(pCat, nCatSize); //и ещё раз, копию.
			}
		}
	}

	delete [] pCat;
	return bRet;
}
//в норде в логических дисках начальные блоки даются относительно начала образа, а не лог. диска
bool CBKDNord::SeekToBeginFile(CFile *f, FileRecord *fr)
{
	bool bRet = true;
	ULONG t = fr->nStartBlock * 512;

	if (t != f->Seek(t, CFile::begin))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		bRet = false;
	}

	return bRet;
}

