/*
	Самый быстрый INI ридер/райтер


	ЧТО ЭТО
		- Это инклуд-файл для PAWN скриптов мультиплеера GTA San Andreas (SA-MP)

	КАК ИСПОЛЬЗОВАТЬ
		- Поместить этот файл в папку include, которая лежит рядом с компилятором
		- В самом верху вашего скрипта добавить #include <mxINI.inc>

	АВТОР
		 MX_Master

	ВЕРСИЯ
		0.4 от 25.01.2009


	ДОСТУПНЫЕ ИНСТРУМЕНТЫ
	более подробные описания даны перед каждой функцией ниже в коде

		ini_createFile ( "путь/к/файлу.ini", "Содержимое файла по умолчанию" )

		ini_openFile ( "путь/к/файлу.ini" )

		ini_closeFile ( ИД_открытого_файла )
	--
		ini_setString ( ИД_открытого_файла, "имя ключа", "текстовое значение" )

		ini_setInteger ( ИД_открытого_файла, "имя ключа", 123456 )

		ini_setFloat ( ИД_открытого_файла, "имя ключа", 3.1416 )
	--
		ini_getString ( ИД_открытого_файла, "имя ключа", returnValue )

		ini_getInteger ( ИД_открытого_файла, "имя ключа", returnValue )

		ini_getFloat ( ИД_открытого_файла, "имя ключа", returnValue )
	--
		ini_removeKey ( ИД_открытого_файла, "имя ключа" )


	ПРИМЕРЫ

		new iniFile = ini_createFile ( "test4268.ini" );

		if ( iniFile < 0 )
			iniFile = ini_openFile ( "test4268.ini" );

		if ( iniFile >= 0 )
		{
			new returnString[32], returnNumber, Float: returnFloat;

			ini_setString  ( iniFile, "ключ со строкой", "текстовое значение" );
			ini_setInteger ( iniFile, "ключ с числом",   123456 );
			ini_setFloat   ( iniFile, "ключ с дробью",   3.1416 );

			ini_removeKey  ( iniFile, "ключ с числом" );

			ini_getString  ( iniFile, "ключ со строкой", returnString );
			ini_getInteger ( iniFile, "ключ с числом",   returnNumber );
			ini_getFloat   ( iniFile, "ключ с дробью",   returnFloat );


			ini_closeFile  ( iniFile );

			printf ( "\n `ключ_со_строкой` = `%s`,\n `ключ_с_числом` = `%d`,\n `ключ_с_дробью` = `%f` \n",
				returnString, returnNumber, returnFloat );
		}
		else print( "\n Не удалось открыть INI файл \n" );
*/











const

	// настройки

	// РЕКОМЕНДУЕТСЯ ИЗМЕНИТЬ ПОД СЕБЯ
	INI_MAX_OPENED_FILES	= 10,		// максимум открытых файлов
	INI_MAX_FILE_SIZE		= 65536,	// байт,		макс. размер файла
	INI_MAX_FILENAME_SIZE	= 256,		// символов,	макс. размер пути к файлу
	INI_MAX_KEYS_IN_FILE	= 2048,		// максимум ключей в открытом файле

	// в ОЗУ будет выделено для временного хранения файлов примерно вот столько байт:
	// INI_MAX_OPENED_FILES * ( INI_MAX_FILE_SIZE + INI_MAX_FILENAME_SIZE*4 + INI_MAX_KEYS_IN_FILE*4 )


	// НЕЛЬЗЯ МЕНЯТЬ
	INI_INTEGER_SIZE		= 12,		// размер строки с целочисленным значением
	INI_FLOAT_SIZE			= 40,		// размер строки с дробным числовым значением

	INI_STRING_DELIMITER	= '\n',		// разделитель строк
	INI_DELIMITER			= '=',		// разделитель ключа и значения


	// коды ошибок, возвращаемые функциями

	// РЕКОМЕНДУЕТСЯ НЕ ИЗМЕНЯТЬ
	INI_OK					=  0,		// функция успешно выполнена

		// проверять на ошибку можно так:
		// if ( возвращаемое_значение < 0 ) ...

	INI_FILE_NOT_FOUND		= -1,		// файл не найден по указанному пути
	INI_FILE_ALREADY_EXIST	= -2,		// файл не найден по указанному пути
	INI_TOO_LARGE_FILE		= -3,		// размер файла превысил допустимый лимит
	INI_WRONG_PATH_SIZE		= -4,		// неправильный размер пути к файлу
	INI_READ_ERROR			= -5,		// ошибка чтения файла
	INI_WRITE_ERROR			= -6,		// ошибка при записи в файл
	INI_NO_FREE_SLOT		= -7,		// нет свободного слота для открытия файла
	INI_WRONG_SLOT			= -8,		// указан неверный слот открытого файла
	INI_KEY_NOT_FOUND		= -9,		// ключ в открытом файле не найден


	// другие вспомогательные константы

	// НЕЛЬЗЯ МЕНЯТЬ
	cellbytes = cellbits / charbits;	// кол-во байт в одной ячейке





// списки разных символов для оператора case в одной из функций

// РЕКОМЕНДУЕТСЯ НЕ ИЗМЕНЯТЬ
#define INI_SPACE_CHAR	' ', '\t'						// строковые пробельные символы
#define INI_KEY_STARTS	' ', '\t', '\r', '\0'			// символы, перед началом ключа
#define INI_STRING_ENDS	'\r', '\n', '\0'				// символы, завершающие значение
#define INI_NUMBER_ENDS	' ', '\t', '\r', '\n', '\0'		// символы, завершающие численное значение





static stock

	// временное хранилище открытых файлов

	_ini_nSlotUsed		[ INI_MAX_OPENED_FILES ],								// флаг: занят ли слот
	_ini_nFileChanged	[ INI_MAX_OPENED_FILES ],								// флаг: был ли изменен файл
	_ini_nFileBusy		[ INI_MAX_OPENED_FILES ],								// флаг: изменяется ли в данный момент содержимое файла
	_ini_nFileSize		[ INI_MAX_OPENED_FILES ],								// размер открытого файла
	_ini_nDelimPos		[ INI_MAX_OPENED_FILES ] [ INI_MAX_KEYS_IN_FILE ],		// список позиций INI_DELIMITER
	_ini_nKeysCount		[ INI_MAX_OPENED_FILES ],								// кол-во ключей открытого файла
	_ini_szFilePath		[ INI_MAX_OPENED_FILES ] [ INI_MAX_FILENAME_SIZE ],		// путь к файлу
	_ini_szFileContent	[ INI_MAX_OPENED_FILES ] [ INI_MAX_FILE_SIZE char ];	// контент файла





















/*
	Создает и сразу открывает INI файл для чтения/записи.

	ПОДРОБНЕЕ
		Файл создается только в ОЗУ, и в него записывается строка szDefaultContent.
		В szDefaultContent могут быть и ключи, которые потом парсер также будет видеть.
		Только при закрытии файла, его содержимое будет записано на диск
		по указанному пути к файлу.

	ПАРАМЕТРЫ:
		szFilePath[]		путь к файлу
		szDefaultContent	контент файла по умолчанию, обычно, можно не указывать

	ВЕРНЕТ:
		код ошибки < 0 или ИД_открытого_файла
*/

stock ini_createFile ( szFilePath[], szDefaultContent[] = "" )
{
	//
	//	несколько блоков с проверками
	//

	// ------------
	new nFileNameSize = strlen( szFilePath ); // узнаем размер пути

	// если размер пути неправильный
	if ( nFileNameSize <= 0 || nFileNameSize >= INI_MAX_FILENAME_SIZE )
		return INI_WRONG_PATH_SIZE; // вернем код ошибки
	// ------------


	// ------------
	if ( fexist( szFilePath ) ) // если файл уже существует
		return INI_FILE_ALREADY_EXIST; // вернем код ошибки - файл уже существует

	for ( new slot = 0; slot < INI_MAX_OPENED_FILES; slot++ ) // перебор всех слотов ОЗУ
		if // если уже есть такой открытый файл
		(
				_ini_nSlotUsed[slot] != 0
			&&	strcmp( szFilePath, _ini_szFilePath[slot], false ) == 0
		)
			return INI_FILE_ALREADY_EXIST; // вернем код ошибки - файл уже существует
	// ------------



	// ------------
	new nFileSize = strlen( szDefaultContent ); // узнаем размер контентa файла по умолчанию

	// если размер неправильный
	if ( nFileSize < 0 || nFileSize >= INI_MAX_FILE_SIZE )
		return INI_TOO_LARGE_FILE; // вернем код ошибки
	// ------------




	//
	//	поиск свободного слота для записи в ОЗУ
	//

	for ( new slot = 0; slot < INI_MAX_OPENED_FILES; slot++ ) // перебор всех слотов ОЗУ
	{
		if ( _ini_nSlotUsed[slot] != 1 ) // если слот найден
		{
			// ------------
			_ini_nSlotUsed[slot]		= 1; // застолбим найденное место
			_ini_nFileChanged[slot]		= 0; // файл не был изменен
			_ini_nFileSize[slot]		= nFileSize; // скопируем размер файла в слот
			_ini_nKeysCount[slot]		= 0; // кол-во ключей выставим 0
			// ------------


			// ------------
			// скопируем в ОЗУ весь файл,
			// запомнив все позиции INI_DELIMITER и их кол-во
			for ( new i = 0;  i < nFileSize && i < INI_MAX_FILE_SIZE;  i++ )
			{
				if (
						szDefaultContent[i] == INI_DELIMITER // если символ это INI_DELIMITER
					&&	_ini_nKeysCount[slot] < INI_MAX_KEYS_IN_FILE // и лимит ключей еще не исчерпан
				) {
					_ini_nDelimPos[slot][ _ini_nKeysCount[slot] ] = i; // добавим еще одну позицию INI_DELIMITER
					_ini_nKeysCount[slot]++; // увеличим кол-во найденных ключей
				}

				_ini_szFileContent[slot]{i} = szDefaultContent[i];
			}

			_ini_szFileContent[slot]{nFileSize} = 0; // символ конца строки для контента
			// ------------


			// ------------
			// скопируем в озу путь к файлу
			memcpy( _ini_szFilePath[slot], szFilePath, 0, nFileNameSize * cellbytes );

			_ini_szFilePath[slot][nFileNameSize] = 0; // символ конца строки для пути
			// ------------


			return slot; // вернем ИД слота
		}
	}




	//
	//	если свободный слот в памяти не найден
	//

	return INI_NO_FREE_SLOT; // вернем код ошибки
	// ------------
}




/*
	Открывает INI файл для чтения/записи, если он существует.

	ПОДРОБНЕЕ
		Копирует в ОЗУ с диска всё содержимое файла, если его размер не превышает
		допустимый. Если кол-во ключей в файле больше допустимого, ошибки никакой не будет,
		просто при чтении/записи значений, парсер не будет видеть лишние ключи.

	ПАРАМЕТРЫ:
		szFilePath[]	путь к файлу

	ВЕРНЕТ:
		код ошибки < 0 или ИД_открытого_файла
*/

stock ini_openFile ( szFilePath[] )
{
	//
	//	несколько блоков с проверками
	//

	// ------------
	new nFileNameSize = strlen( szFilePath ); // узнаем размер пути

	// если размер пути неправильный
	if ( nFileNameSize <= 0 || nFileNameSize >= INI_MAX_FILENAME_SIZE )
		return INI_WRONG_PATH_SIZE; // вернем код ошибки
	// ------------


	if ( ! fexist( szFilePath ) ) // если файл не найден
		return INI_FILE_NOT_FOUND; // вернем код ошибки


	// ------------
	// проверка - открыт ли уже файл с таким именем
	for ( new slot = 0; slot < INI_MAX_OPENED_FILES; slot++ ) // перебор всех слотов ОЗУ
		if // если уже есть такой открытый файл
		(
				_ini_nSlotUsed[slot] != 0
			&&	strcmp( szFilePath, _ini_szFilePath[slot], false ) == 0
		)
			return slot; // просто вернем его слот
	// ------------


	// ------------
	new File: pFile = fopen( szFilePath, io_read ); // пытаемся открыть файл для чтения

	if ( ! pFile ) // если файл не открылся
		return INI_READ_ERROR; // вернем код ошибки
	// ------------


	// ------------
	new nFileSize = flength( pFile ); // узнаем размер файла

	if ( nFileSize >= INI_MAX_FILE_SIZE ) // если размер файла слишком большой
	{
		fclose(pFile); // закроем файл
		return INI_TOO_LARGE_FILE; // вернем код ошибки
	}
	// ------------




	//
	//	поиск свободного слота для записи в ОЗУ
	//

	// ------------
	for ( new slot = 0; slot < INI_MAX_OPENED_FILES; slot++ ) // перебор всех слотов ОЗУ
	{
		if ( _ini_nSlotUsed[slot] != 1 ) // если слот найден
		{
			// ------------
			_ini_nSlotUsed[slot]		= 1; // застолбим найденное место
			_ini_nFileChanged[slot]		= 0; // файл не был изменен
			_ini_nFileSize[slot]		= nFileSize; // скопируем размер файла в слот
			_ini_nKeysCount[slot]		= 0; // кол-во ключей выставим 0
			// ------------


			// ------------
			// скопируем в ОЗУ весь файл,
			// запомнив все позиции INI_DELIMITER и их кол-во
			for ( new i = 0, symbol; i < nFileSize && i < INI_MAX_FILE_SIZE; i++ )
			{
				symbol = fgetchar( pFile, 0, false ); // читаем из файла следующий символ

				if (
						symbol == INI_DELIMITER // если символ это INI_DELIMITER
					&&	_ini_nKeysCount[slot] < INI_MAX_KEYS_IN_FILE // и лимит ключей еще не исчерпан
				) {
					_ini_nDelimPos[slot][ _ini_nKeysCount[slot] ] = i; // добавим еще одну позицию INI_DELIMITER
					_ini_nKeysCount[slot]++; // увеличим кол-во найденных ключей
				}

				_ini_szFileContent[slot]{i} = symbol;
			}

			_ini_szFileContent[slot]{nFileSize} = 0; // символ конца строки для контента

			fclose(pFile); // закроем файл
			// ------------


			// ------------
			// скопируем в озу путь к файлу
			memcpy( _ini_szFilePath[slot], szFilePath, 0, nFileNameSize * cellbytes );

			_ini_szFilePath[slot][nFileNameSize] = 0; // символ конца строки для пути
			// ------------


			return slot; // вернем ИД слота
		}
	}
	// ------------




	//
	//	если свободный слот в памяти не найден
	//

	// ------------
	fclose(pFile); // закроем файл

	return INI_NO_FREE_SLOT; // вернем код ошибки
	// ------------
}




/*
	Закрывает INI файл, если он был открыт.

	ПОДРОБНЕЕ
		Если файл не был изменен - освободит слот для хранения файла в ОЗУ.
		Если файл был изменен - полностью перезапишет файл на диске.

	ПАРАМЕТРЫ:
		nFilePointer	ИД_открытого_файла, полученный от ini_openFile / ini_createFile

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_closeFile ( nFilePointer )
{
	if
	(	// если ИД открытого файла указан верно
			nFilePointer >= 0
		&&	nFilePointer < INI_MAX_OPENED_FILES
		&&	_ini_nSlotUsed[nFilePointer] != 0
	)
	{
		if ( _ini_nFileChanged[nFilePointer] != 0 ) // если файл был изменен
		{
			new File: pFile = fopen( _ini_szFilePath[nFilePointer], io_write ); // пытаемся открыть файл для записи

			if ( ! pFile ) // если файл не открылся
				return INI_WRITE_ERROR; // вернем код ошибки

			// запишем контент файла из ОЗУ на диск
			for ( new i = 0;  i < _ini_nFileSize[nFilePointer];  i++ )
				fputchar( pFile, _ini_szFileContent[nFilePointer]{i}, false );

			fclose(pFile); // закроем файл
		}

		_ini_nSlotUsed[nFilePointer] = 0; // освободить слот открытого файла

		return INI_OK; // вернуть код об успешном выполнении функции
	}

	return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
}











/*
	Получает из открытого INI файла значение указанного ключа.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и помещает в
		szReturnValue его строковое значение. szReturnValue нужно создать заранее.
		nSizeOfReturn можно не указывать, если только не нужно точное кол-во
		возвращаемых символов в строке.
		Как ключ так и значение в файле, могут быть любой длины и
		могут содержать любые символы, кроме 2 символов конца строки \r и \n.
		Парсер не видит пробелы и знаки табуляции вокруг имени ключа и
		перед значением, он считает их отступами.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		szReturnValue[]		сюда будет помещено значение ключа в виде строки
		nSizeOfReturn		макс. размер возвращаемой строки, обычно, это размер szReturnValue

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_getString ( nFilePointer, szKeyName[], szReturnValue[], nSizeOfReturn = sizeof(szReturnValue) )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found = 0;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		// ----------------
		if ( found != 0 ) // если указанный ключ найден в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			// скопируем посимвольно в szReturnValue значение ключа из файла
			// воспользуемся созданной переменной found как позицией в возвращаемом значении
			for ( found = 0;  found < nSizeOfReturn;  found++, curFilePos++ )
			{
				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_STRING_ENDS : // если это символ конца значения
					{
						szReturnValue[found] = 0; // запишем символ конца строки
						break; // конец копирования
					}
					default :
						// копируем символ из файла в szReturnValue
						szReturnValue[found] = _ini_szFileContent[nFilePointer]{curFilePos};
				}
			}

			szReturnValue[nSizeOfReturn - 1] = 0; // на всякий случай обрежем правильно строку

			return INI_OK;
			// ----------------
		}
		// ----------------
	}
	// ----------------


	// ----------------
	return INI_KEY_NOT_FOUND;
	// ----------------
}




/*
	Получает из открытого INI файла целочисленное значение указанного ключа.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и помещает в
		nReturnValue его целочисленное значение. nReturnValue нужно создать заранее.
		Имя ключа в файле может быть любой длины, а также может содержать
		любые символы, кроме 2 символов конца строки \r и \n.
		Значение может быть только фиксированной длины, которая равна INI_INTEGER_SIZE - 1.
		Парсер не видит пробелы и знаки табуляции вокруг имени ключа/значения,
		он считает их отступами.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		nReturnValue		сюда будет помещено значение ключа в виде целого числа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_getInteger ( nFilePointer, szKeyName[], & nReturnValue )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found = 0;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		// ----------------
		if ( found != 0 ) // если указанный ключ найден в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			new strValue[INI_INTEGER_SIZE]; // временная строка для численного значения

			// скопируем посимвольно в strValue значение ключа из файла
			// воспользуемся созданной переменной found как позицией в возвращаемом значении
			for ( found = 0;  found < INI_INTEGER_SIZE;  found++, curFilePos++ )
			{
				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_NUMBER_ENDS : // если это символ конца численного значения
					{
						strValue[found] = 0; // запишем символ конца строки
						break; // конец копирования
					}
					default :
						// копируем символ из файла в strValue
						strValue[found] = _ini_szFileContent[nFilePointer]{curFilePos};
				}
			}

			strValue[INI_INTEGER_SIZE - 1] = 0; // на всякий случай обрежем правильно строку

			nReturnValue = strval(strValue); // запишем в nReturnValue численное значение ключа

			return INI_OK;
			// ----------------
		}
		// ----------------
	}
	// ----------------


	// ----------------
	return INI_KEY_NOT_FOUND;
	// ----------------
}




/*
	Получает из открытого INI файла дробное численное значение указанного ключа.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и помещает в
		fReturnValue его дробное значение. fReturnValue нужно создать заранее.
		Имя ключа в файле может быть любой длины, а также может содержать
		любые символы, кроме 2 символов конца строки \r и \n.
		Значение может быть только фиксированной длины, которая равна INI_FLOAT_SIZE - 1.
		Парсер не видит пробелы и знаки табуляции вокруг имени ключа/значения,
		он считает их отступами.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		fReturnValue		сюда будет помещено значение ключа в виде дробного числа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_getFloat ( nFilePointer, szKeyName[], & Float: fReturnValue )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found = 0;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		// ----------------
		if ( found != 0 ) // если указанный ключ найден в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			new strValue[INI_FLOAT_SIZE]; // временная строка для дробного значения

			// скопируем посимвольно в strValue значение ключа из файла
			// воспользуемся созданной переменной found как позицией в возвращаемом значении
			for ( found = 0;  found < INI_INTEGER_SIZE;  found++, curFilePos++ )
			{
				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_NUMBER_ENDS : // если это символ конца численного значения
					{
						strValue[found] = 0; // запишем символ конца строки
						break; // конец копирования
					}
					default :
						// копируем символ из файла в strValue
						strValue[found] = _ini_szFileContent[nFilePointer]{curFilePos};
				}
			}

			strValue[INI_FLOAT_SIZE - 1] = 0; // на всякий случай обрежем правильно строку

			fReturnValue = floatstr(strValue); // запишем в fReturnValue дробное значение ключа

			return INI_OK;
			// ----------------
		}
		// ----------------
	}
	// ----------------


	// ----------------
	return INI_KEY_NOT_FOUND;
	// ----------------
}











/*
	Изменяет/добавляет в открытый INI файл указанный ключ и его значение.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и изменяет его
		строковое значение на szKeyValue. Форматирование файла не меняется,
		если только указанное имя ключа не было найдено, тогда ключ и значение
		будут добавлены в конец файла.
		Имя ключа/значение в файле может быть любой длины, а также может содержать
		любые символы, кроме 2 символов конца строки \r и \n. Если эти символы есть
		в имени ключа или в значении, вы должны должны хорошо знать и осознавать
		последствия.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		szKeyValue[]		строка со значением ключа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_setString ( nFilePointer, szKeyName[], szKeyValue[] )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		if ( found != 0 ) // если указанный ключ НАЙДЕН в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			new nValueLen = strlen(szKeyValue); // узнаем размер значения указанного ключа

			// если будущий размер файла превышает лимит
			if ( ( curFilePos + nValueLen ) >= INI_MAX_FILE_SIZE )
				return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
			// ----------------


			// ----------------
			new fileValueStartPos = curFilePos; // сохраним позицию начала значения

			// ищем позицию конца значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_STRING_ENDS :	break; // если это конец строки - стоп
					default :				continue; // если это другой символ, перейдем к след. символу
				}
			}

			// вычислим смещение оставшихся позиций для INI_DELIMITER после изменения значения
			new filePosOffset = nValueLen - ( /*текущ длина значения*/ curFilePos - fileValueStartPos );
			// ----------------


			if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
				return INI_WRITE_ERROR; // вернем код ошибки при записи в файл


			// ----------------
			_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1

			if ( filePosOffset != 0 ) // если длины старого и нового значений разные
			{
				if ( filePosOffset < 0 ) // если длина нового значения меньше
				{
					for // копируем символы, стоящие после текущего значения на их новые места,
					( // которые теперь будут ближе к началу файла
						;
						curFilePos < _ini_nFileSize[nFilePointer];
						curFilePos++
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}
				else if ( filePosOffset > 0 ) // если длина нового значения больше
				{
					new fileValueEndPos = curFilePos;

					for // копируем символы, стоящие после текущего значения на их новые места,
					( // начиная с конца файла
						curFilePos = _ini_nFileSize[nFilePointer] - 1;
						curFilePos >= fileValueEndPos;
						curFilePos--
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}

				// изменим позиции всех INI_DELIMITER, которые находились после текущего INI_DELIMITER
				for ( kPos++;  kPos < _ini_nKeysCount[nFilePointer];  kPos++ )
					_ini_nDelimPos[nFilePointer][kPos] += filePosOffset;

				_ini_nFileSize[nFilePointer] += filePosOffset; // изменим размер файла
				_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] } = 0; // конец строки для конца файла
			}

			curFilePos = fileValueStartPos; // текущ поз файла = поз начала старого значения
			for // запишем новое значение поверх старого
			(
				new valuePos = 0;
				valuePos < nValueLen;
				curFilePos++, valuePos++
			)
				_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[valuePos];

			_ini_nFileChanged[nFilePointer]		= 1; // флаг: файл изменен = 1
			_ini_nFileBusy[nFilePointer]		= 0; // флаг: файл изменяется = 0

			return INI_OK;
			// ----------------
		}
	}
	// ----------------


	//
	// если указанный ключ НЕ НАЙДЕН в файле
	//

	// ----------------
	new nValueLen = strlen(szKeyValue); // узнаем размер значения ключа

	// если будущий размер файла превышает лимит
	if ( ( _ini_nFileSize[nFilePointer] + 5 + nKeyLen + nValueLen ) >= INI_MAX_FILE_SIZE )
		return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
	// ----------------


	// ----------------
	if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
		return INI_WRITE_ERROR; // вернем код ошибки при записи в файл

	_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1
	// ----------------


	// ----------------
	if // если последний символ файла не является разделителем строк
	(
			_ini_nFileSize[nFilePointer] > 0
		&&	_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] - 1 } != INI_STRING_DELIMITER
	)
	{
		// вставим в конец файла перевод каретки и разделитель строк
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] }		= '\r';
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] + 1 }	= INI_STRING_DELIMITER;

		_ini_nFileSize[nFilePointer] += 2; // увеличим размер файла на 2
	}

	new curFilePos, strPos; // будут временным хранилищами позиций в файле/ключе/значении

	for // добавим посимвольно имя ключа в файл
	(
		curFilePos = _ini_nFileSize[nFilePointer], strPos = 0;
		strPos < nKeyLen;
		curFilePos++, strPos++
	)
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyName[strPos];

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после ключа добавим пробел

	_ini_nDelimPos[nFilePointer][ _ini_nKeysCount[nFilePointer] ]	= curFilePos; // добавим новую позицию INI_DELIMITER
	_ini_nKeysCount[nFilePointer]++; // кол-во ключей в файле +1

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= INI_DELIMITER; // после пробела выше добавим INI_DELIMITER
	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после INI_DELIMITER добавим пробел

	// добавим посимвольно значение ключа в файл
	for ( strPos = 0;  strPos < nValueLen;  curFilePos++, strPos++ )
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[strPos];

	_ini_szFileContent[nFilePointer]{curFilePos}	= 0; // добавим символ конца строки
	_ini_nFileSize[nFilePointer]					= curFilePos; // изменим размер файла
	_ini_nFileChanged[nFilePointer]					= 1; // флаг: файл изменен = 1
	_ini_nFileBusy[nFilePointer]					= 0; // флаг: файл изменяется = 0

	return INI_OK;
	// ----------------
}




/*
	Изменяет/добавляет в открытый INI файл указанный ключ и его целочисленное значение.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и изменяет его
		целочисленное значение на nKeyValue. Форматирование файла не меняется,
		если только указанное имя ключа не было найдено, тогда ключ и значение
		будут добавлены в конец файла.
		Имя ключа в файле может быть любой длины, а также может содержать
		любые символы, кроме 2 символов конца строки \r и \n. Если вы в качестве
		значения указываете число больше, чем cellmax или меньше, чем cellmin,
		вы должны должны хорошо знать и осознавать последствия.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		nKeyValue			целочисленное значение ключа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_setInteger ( nFilePointer, szKeyName[], nKeyValue )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		if ( found != 0 ) // если указанный ключ НАЙДЕН в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			new szKeyValue[INI_INTEGER_SIZE]; // создадим строку для хранения целочисленного значения
			format( szKeyValue, INI_INTEGER_SIZE, "%i", nKeyValue );
			// ----------------


			// ----------------
			new nValueLen = strlen(szKeyValue); // узнаем размер значения указанного ключа

			// если будущий размер файла превышает лимит
			if ( ( curFilePos + nValueLen ) >= INI_MAX_FILE_SIZE )
				return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
			// ----------------


			// ----------------
			new fileValueStartPos = curFilePos; // сохраним позицию начала значения

			// ищем позицию конца значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_NUMBER_ENDS :	break; // если это конец числа - стоп
					default :				continue; // если это другой символ, перейдем к след. символу
				}
			}

			// вычислим смещение оставшихся позиций для INI_DELIMITER после изменения значения
			new filePosOffset = nValueLen - ( /*текущ длина значения*/ curFilePos - fileValueStartPos );
			// ----------------


			if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
				return INI_WRITE_ERROR; // вернем код ошибки при записи в файл


			// ----------------
			_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1

			if ( filePosOffset != 0 ) // если длины старого и нового значений разные
			{
				if ( filePosOffset < 0 ) // если длина нового значения меньше
				{
					for // копируем символы, стоящие после текущего значения на их новые места,
					( // которые теперь будут ближе к началу файла
						;
						curFilePos < _ini_nFileSize[nFilePointer];
						curFilePos++
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}
				else if ( filePosOffset > 0 ) // если длина нового значения больше
				{
					new fileValueEndPos = curFilePos;

					for // копируем символы, стоящие после текущего значения на их новые места,
					( // начиная с конца файла
						curFilePos = _ini_nFileSize[nFilePointer] - 1;
						curFilePos >= fileValueEndPos;
						curFilePos--
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}

				// изменим позиции всех INI_DELIMITER, которые находились после текущего INI_DELIMITER
				for ( kPos++;  kPos < _ini_nKeysCount[nFilePointer];  kPos++ )
					_ini_nDelimPos[nFilePointer][kPos] += filePosOffset;

				_ini_nFileSize[nFilePointer] += filePosOffset; // изменим размер файла
				_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] } = 0; // конец строки для конца файла
			}

			curFilePos = fileValueStartPos; // текущ поз файла = поз начала старого значения
			for // запишем новое значение поверх старого
			(
				new valuePos = 0;
				valuePos < nValueLen;
				curFilePos++, valuePos++
			)
				_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[valuePos];

			_ini_nFileChanged[nFilePointer]		= 1; // флаг: файл изменен = 1
			_ini_nFileBusy[nFilePointer]		= 0; // флаг: файл изменяется = 0

			return INI_OK;
			// ----------------
		}
	}
	// ----------------


	//
	// если указанный ключ НЕ НАЙДЕН в файле
	//

	// ----------------
	new szKeyValue[INI_INTEGER_SIZE]; // создадим строку для хранения целочисленного значения
	format( szKeyValue, INI_INTEGER_SIZE, "%i", nKeyValue );
	// ----------------


	// ----------------
	new nValueLen = strlen(szKeyValue); // узнаем размер значения ключа

	// если будущий размер файла превышает лимит
	if ( ( _ini_nFileSize[nFilePointer] + 5 + nKeyLen + nValueLen ) >= INI_MAX_FILE_SIZE )
		return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
	// ----------------


	// ----------------
	if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
		return INI_WRITE_ERROR; // вернем код ошибки при записи в файл

	_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1
	// ----------------


	// ----------------
	if // если последний символ файла не является разделителем строк
	(
			_ini_nFileSize[nFilePointer] > 0
		&&	_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] - 1 } != INI_STRING_DELIMITER
	)
	{
		// вставим в конец файла перевод каретки и разделитель строк
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] }		= '\r';
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] + 1 }	= INI_STRING_DELIMITER;

		_ini_nFileSize[nFilePointer] += 2; // увеличим размер файла на 2
	}

	new curFilePos, strPos; // будут временным хранилищами позиций в файле/ключе/значении

	for // добавим посимвольно имя ключа в файл
	(
		curFilePos = _ini_nFileSize[nFilePointer], strPos = 0;
		strPos < nKeyLen;
		curFilePos++, strPos++
	)
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyName[strPos];

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после ключа добавим пробел

	_ini_nDelimPos[nFilePointer][ _ini_nKeysCount[nFilePointer] ]	= curFilePos; // добавим новую позицию INI_DELIMITER
	_ini_nKeysCount[nFilePointer]++; // кол-во ключей в файле +1

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= INI_DELIMITER; // после пробела выше добавим INI_DELIMITER
	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после INI_DELIMITER добавим пробел

	// добавим посимвольно значение ключа в файл
	for ( strPos = 0;  strPos < nValueLen;  curFilePos++, strPos++ )
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[strPos];

	_ini_szFileContent[nFilePointer]{curFilePos}	= 0; // добавим символ конца строки
	_ini_nFileSize[nFilePointer]					= curFilePos; // изменим размер файла
	_ini_nFileChanged[nFilePointer]					= 1; // флаг: файл изменен = 1
	_ini_nFileBusy[nFilePointer]					= 0; // флаг: файл изменяется = 0

	return INI_OK;
	// ----------------
}




/*
	Изменяет/добавляет в открытый INI файл указанный ключ и его дробное численное значение.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и изменяет его
		дробное значение на fKeyValue. Форматирование файла не меняется,
		если только указанное имя ключа не было найдено, тогда ключ и значение
		будут добавлены в конец файла.
		Имя ключа в файле может быть любой длины, а также может содержать
		любые символы, кроме 2 символов конца строки \r и \n. Если вы в качестве
		значения указываете очень большое/маленькое дробное число, вы должны
		хорошо знать и осознавать последствия.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа
		fKeyValue			дробное численное значение ключа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_setFloat ( nFilePointer, szKeyName[], Float: fKeyValue )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		if ( found != 0 ) // если указанный ключ НАЙДЕН в файле
		{
			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			new szKeyValue[INI_FLOAT_SIZE]; // создадим строку для хранения дробного значения
			format( szKeyValue, INI_FLOAT_SIZE, "%f", fKeyValue ); // поместим строковое значение fKeyValue в szKeyValue
			// ----------------


			// ----------------
			new nValueLen = strlen(szKeyValue); // узнаем размер значения указанного ключа

			// если будущий размер файла превышает лимит
			if ( ( curFilePos + nValueLen ) >= INI_MAX_FILE_SIZE )
				return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
			// ----------------


			// ----------------
			new fileValueStartPos = curFilePos; // сохраним позицию начала значения

			// ищем позицию конца значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_NUMBER_ENDS :	break; // если это конец числа - стоп
					default :				continue; // если это другой символ, перейдем к след. символу
				}
			}

			// вычислим смещение оставшихся позиций для INI_DELIMITER после изменения значения
			new filePosOffset = nValueLen - ( /*текущ длина значения*/ curFilePos - fileValueStartPos );
			// ----------------


			if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
				return INI_WRITE_ERROR; // вернем код ошибки при записи в файл


			// ----------------
			_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1

			if ( filePosOffset != 0 ) // если длины старого и нового значений разные
			{
				if ( filePosOffset < 0 ) // если длина нового значения меньше
				{
					for // копируем символы, стоящие после текущего значения на их новые места,
					( // которые теперь будут ближе к началу файла
						;
						curFilePos < _ini_nFileSize[nFilePointer];
						curFilePos++
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}
				else if ( filePosOffset > 0 ) // если длина нового значения больше
				{
					new fileValueEndPos = curFilePos;

					for // копируем символы, стоящие после текущего значения на их новые места,
					( // начиная с конца файла
						curFilePos = _ini_nFileSize[nFilePointer] - 1;
						curFilePos >= fileValueEndPos;
						curFilePos--
					)
						_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
							_ini_szFileContent[nFilePointer]{curFilePos};
				}

				// изменим позиции всех INI_DELIMITER, которые находились после текущего INI_DELIMITER
				for ( kPos++;  kPos < _ini_nKeysCount[nFilePointer];  kPos++ )
					_ini_nDelimPos[nFilePointer][kPos] += filePosOffset;

				_ini_nFileSize[nFilePointer] += filePosOffset; // изменим размер файла
				_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] } = 0; // конец строки для конца файла
			}

			curFilePos = fileValueStartPos; // текущ поз файла = поз начала старого значения
			for // запишем новое значение поверх старого
			(
				new valuePos = 0;
				valuePos < nValueLen;
				curFilePos++, valuePos++
			)
				_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[valuePos];

			_ini_nFileChanged[nFilePointer]		= 1; // флаг: файл изменен = 1
			_ini_nFileBusy[nFilePointer]		= 0; // флаг: файл изменяется = 0

			return INI_OK;
			// ----------------
		}
	}
	// ----------------


	//
	// если указанный ключ НЕ НАЙДЕН в файле
	//

	// ----------------
	new szKeyValue[INI_FLOAT_SIZE]; // создадим строку для хранения дробного значения
	format( szKeyValue, INI_FLOAT_SIZE, "%f", fKeyValue ); // поместим строковое значение fKeyValue в szKeyValue
	// ----------------


	// ----------------
	new nValueLen = strlen(szKeyValue); // узнаем размер значения ключа

	// если будущий размер файла превышает лимит
	if ( ( _ini_nFileSize[nFilePointer] + 5 + nKeyLen + nValueLen ) >= INI_MAX_FILE_SIZE )
		return INI_TOO_LARGE_FILE; // вернем код ошибки о переполнении файла
	// ----------------


	// ----------------
	if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
		return INI_WRITE_ERROR; // вернем код ошибки при записи в файл

	_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1
	// ----------------


	// ----------------
	if // если последний символ файла не является разделителем строк
	(
			_ini_nFileSize[nFilePointer] > 0
		&&	_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] - 1 } != INI_STRING_DELIMITER
	)
	{
		// вставим в конец файла перевод каретки и разделитель строк
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] }		= '\r';
		_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] + 1 }	= INI_STRING_DELIMITER;

		_ini_nFileSize[nFilePointer] += 2; // увеличим размер файла на 2
	}

	new curFilePos, strPos; // будут временным хранилищами позиций в файле/ключе/значении

	for // добавим посимвольно имя ключа в файл
	(
		curFilePos = _ini_nFileSize[nFilePointer], strPos = 0;
		strPos < nKeyLen;
		curFilePos++, strPos++
	)
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyName[strPos];

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после ключа добавим пробел

	_ini_nDelimPos[nFilePointer][ _ini_nKeysCount[nFilePointer] ]	= curFilePos; // добавим новую позицию INI_DELIMITER
	_ini_nKeysCount[nFilePointer]++; // кол-во ключей в файле +1

	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= INI_DELIMITER; // после пробела выше добавим INI_DELIMITER
	_ini_szFileContent[nFilePointer]{ curFilePos++ }				= ' '; // после INI_DELIMITER добавим пробел

	// добавим посимвольно значение ключа в файл
	for ( strPos = 0;  strPos < nValueLen;  curFilePos++, strPos++ )
		_ini_szFileContent[nFilePointer]{curFilePos} = szKeyValue[strPos];

	_ini_szFileContent[nFilePointer]{curFilePos}	= 0; // добавим символ конца строки
	_ini_nFileSize[nFilePointer]					= curFilePos; // изменим размер файла
	_ini_nFileChanged[nFilePointer]					= 1; // флаг: файл изменен = 1
	_ini_nFileBusy[nFilePointer]					= 0; // флаг: файл изменяется = 0

	return INI_OK;
	// ----------------
}




/*
	Удаляет из открытого INI файла указанный ключ и его значение.

	ПОДРОБНЕЕ
		Парсер ищет в ОЗУ в контенте файла указанный ключ и удаляет найденную
		пару ключ/значение. Имя ключа/значение в файле может быть любой длины,
		а также может содержать любые символы, кроме 2 символов конца строки \r и \n.
		Если эти символы есть в имени ключа или в значении,
		вы должны должны хорошо знать и осознавать последствия.

	ПАРАМЕТРЫ:
		nFilePointer		ИД_открытого_файла, полученный от ini_openFile / ini_createFile
		szKeyName[]			имя ключа

	ВЕРНЕТ:
		код ошибки < 0 или 0 при успехе
*/

stock ini_removeKey ( nFilePointer, szKeyName[] )
{
	// ----------------
	if // если ИД открытого файла указан неверно
	(
			nFilePointer < 0
		||	nFilePointer >= INI_MAX_OPENED_FILES
		||	_ini_nSlotUsed[nFilePointer] != 1
	)
		return INI_WRONG_SLOT; // вернуть код: неверный указатель на открытый файл
	// ----------------


	// ----------------
	new nKeyLen = strlen(szKeyName); // узнаем длину имени указанного ключа

	if ( nKeyLen <= 0 ) // если указан пустой ключ
		return INI_KEY_NOT_FOUND;
	// ----------------


	// ----------------
	for // перебор и сравнение всех ключей файла с указанным ключом
	(
		new kPos = 0, curFilePos, found;
		kPos < _ini_nKeysCount[nFilePointer];
		kPos++
	)
	{
		// ----------------
		found = 0; // флаг, найдена ли позиция конца ключа = 0

		for // ищем позицию конца ключа
		(
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] - 1; // текущ. поз. файла = поз. текущ. INI_DELIMITER - 1
			curFilePos >= 0; // продолжать пока поз. файла >= 0
			curFilePos-- // после каждого повтора текущ. поз. файла -= 1
		)
		{
			switch ( _ini_szFileContent[nFilePointer]{curFilePos} ) // узнаем что за символ в текущ. поз. файла
			{
				case INI_SPACE_CHAR :			continue; // если это пробельный символ, перейдем к пред. символу файла
				case INI_STRING_DELIMITER :		break; // если это конец строки
				default : // если это другой символ
				{
					found = 1; // позиция конца ключа найдена
					break; // конец цикла
				}
			}
		}

		// если позиция конца ключа не найдена, переход к след. позиции INI_DELIMITER
		if ( found != 1 ) continue;
		// ----------------


		// сравниваем посимвольно текущий ключ файла и указанный ключ
		for ( new curKeyPos = nKeyLen - 1;  curKeyPos >= 0;  curFilePos--, curKeyPos-- )
		{
			if
			(
					curFilePos < 0 // если поз файла стала < 0
				||	_ini_szFileContent[nFilePointer]{curFilePos} != szKeyName[curKeyPos] // если символы из ключей не равны
				||	_ini_szFileContent[nFilePointer]{curFilePos} == INI_STRING_DELIMITER // если символ из ключа это INI_STRING_DELIMITER
			)
			{
				found = 0; // флаг, ключ не найден
				break; // конец сравнения
			}
		}


		if ( found != 0 ) // если указанный ключ НАЙДЕН в файле
		{
			// сохраним позицию начала ключа
			new keyStartPos = ( curFilePos <= 0 ) ? 0 : curFilePos + 1;


			// ----------------
			// текущая позиция в файле будет на 1 больше текущей позиции INI_DELIMITER
			curFilePos = _ini_nDelimPos[nFilePointer][kPos] + 1;

			// ищем позицию начала значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_SPACE_CHAR :	continue; // если это пробельный символ, перейдем к след. символу
					default :				break; // если это другой символ
				}
			}
			// ----------------


			// ----------------
			// ищем позицию конца значения, она будет помещена в curFilePos
			for ( ; ; curFilePos++ )
			{
				if ( curFilePos >= _ini_nFileSize[nFilePointer] ) break;

				switch ( _ini_szFileContent[nFilePointer]{curFilePos} )
				{
					case INI_STRING_ENDS :	break; // если это конец строки - стоп
					default :				continue; // если это другой символ, перейдем к след. символу
				}
			}

			// вычислим смещение оставшихся позиций для INI_DELIMITER после изменения значения
			new filePosOffset = keyStartPos - curFilePos;
			// ----------------


			if ( _ini_nFileBusy[nFilePointer] != 0 ) // если прямо сейчас файл изменяется другой функцией
				return INI_WRITE_ERROR; // вернем код ошибки при записи в файл


			// ----------------
			_ini_nFileBusy[nFilePointer] = 1; // флаг: файл изменяется = 1

			// затираем контент ключа, следующими за ним данными файла
			for ( ; curFilePos <= _ini_nFileSize[nFilePointer]; curFilePos++ )
				_ini_szFileContent[nFilePointer]{ curFilePos + filePosOffset } =
					_ini_szFileContent[nFilePointer]{curFilePos};

			// затираем текущий INI_DELIMITER, и меняем позиции последующих INI_DELIMITER
			for ( ;  kPos < _ini_nKeysCount[nFilePointer];  kPos++ )
				_ini_nDelimPos[nFilePointer][kPos] = _ini_nDelimPos[nFilePointer][kPos + 1] + filePosOffset;

			_ini_nKeysCount[nFilePointer]--;
			_ini_nFileSize[nFilePointer] += filePosOffset; // изменим размер файла
			_ini_szFileContent[nFilePointer]{ _ini_nFileSize[nFilePointer] } = 0; // конец строки для конца файла

			_ini_nFileChanged[nFilePointer]		= 1; // флаг: файл изменен = 1
			_ini_nFileBusy[nFilePointer]		= 0; // флаг: файл изменяется = 0

			return INI_OK;
			// ----------------
		}
	}
	// ----------------


	//
	// если указанный ключ НЕ НАЙДЕН в файле
	//

	// ----------------
	return INI_KEY_NOT_FOUND;
	// ----------------
}








/*
	Если вы хотите использовать другие имена функций в своем скрипте,
	измените указанные ниже макросы замены на свои и раскомментируйте нужные строки

//      ваше название	оригинал

#define iniCreate		ini_createFile
#define iniOpen			ini_openFile
#define iniClose		ini_closeFile
#define iniSet			ini_setString
#define iniSetInt		ini_setInteger
#define iniSetFloat		ini_setFloat
#define iniGet			ini_getString
#define iniGetInt		ini_getInteger
#define iniGetFloat		ini_getFloat
#define iniRemove		ini_removeKey
*/
#define iniCreate		ini_createFile
#define iniOpen			ini_openFile
#define iniClose		ini_closeFile
#define iniSet			ini_setString
#define iniSetInt		ini_setInteger
#define iniSetFloat		ini_setFloat
#define iniGet			ini_getString
#define iniGetInt		ini_getInteger
#define iniGetFloat		ini_getFloat
#define iniRemove		ini_removeKey
