hex.pp.ua

Кириллица в native-приложении

Пишем на русском в native-режиме Windows




При разработке native-приложений может возникнуть необходимость писать на русском языке, но, как оказалось, добиться этого не так-то просто, так как по-умолчанию NtDisplayString не выводит кириллицу на экран. Для того, чтобы получить возможность вывода русских букв, нужно разобраться, где и в каком формате хранятся глифы символов, которые отображаются на экране. Если подробнее рассмотреть функцию winx_printf (проекте используется библиотека ZenWINX и заголовочные файлы NDK для упрощения разработки приложения), то мы увидим, что она в свою очередь вызывает winx_print, далее вызывается из ntdll.dll функция NtDisplayString, которая преобразует входящую строку с помощью RtlUnicodeStringToOemString, далее идёт вызов функции InbvDisplayString, которая обращается к VGA Boot Driver (bootvid.dll). Отображаемые глифы хранятся в bootvid.dll в следующем формате (на примере английской буквы A):

00000000 – 0×00
00000000 – 0×00
00011000 – 0×18
00011000 – 0×18
00100100 – 0×24
00100100 – 0×24
00100100 – 0×24
01111110 – 0×7E
01000010 – 0×42
10000001 – 0×81
00000000 – 0×00
00000000 – 0×00
00000000 – 0×00

Посмотреть остальные символы можно с помощью следующего нехитрого скрипта на Perl:

open F, '<', 'bootvid.dll' or die $!;
#0x1938 - начало таблицы с глифами в XP SP3
seek F, 0x1938, 1;
read F, my $buf, 13 * 256;
close F;
 
for(my $i = 0; $i < 13 * 256; $i += 13)
{
	my $symbol = substr $buf, $i, 13;
	my $hex = unpack 'H*', $symbol;
	for(my ($j, $k) = (0, length $hex); $j < $k; $j += 2)
	{
		my $line = hex substr $hex, $j, 2;
		printf "%08b - 0x%02X\n", $line, $line;
	}
	print "\n\n";
}

Таким образом, каждый символ имеет размер 8×13 пикселей и, соответственно, занимает 13 байт. Всего под символы отведено 256 * 13 = 3328 байт. То есть, чтобы добавить поддержку русского, необходимо найти начало таблицы глифов в памяти и заменить неиспользуемые символы своими глифами. Начало таблицы может меняться в зависимости от версии ОС, например, в Windows 7 смещение от начала составляет 0×2610, в Vista 0×2420, а в XP SP3 0×1938. Найти таблицу довольно просто, для этого достаточно найти в памяти первый глиф (0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00).

Для начала необходимо составить свою таблицу глифов, чтобы заменить ею часть существующей таблицы. Вручную «рисовать» такое довольно муторно, поэтому следует поступить следующим образом: вывести в консоли windows список необходимых символов, сделал скриншот и преобразовать его в эдакий ASCII-арт. Делается это следующим образом:

use GD;
 
my $im = GD::Image->newFromPng('image.png', 1) or die;
 
for(my $x = 1; $x < $im->width(); $x += 8)
{
	for(my $dy = 0; $dy < 13; $dy++)
	{
		for(my $dx = 0; $dx < 8; $dx++)
		{
			my $index = $im->getPixel($x + $dx, $dy);
			my ($r, undef, undef) = $im->rgb($index);
			if($r == 192)
			{
				print "1";
			}
			else
			{
				print "0";
			}
		}
		print "\n";
	}
	print "\n\n";
}

И сразу же сворачиваем получившуюся таблицу в массив байт:

open F, '<', 'table.txt' or die $!;
chomp(my @lines = <F>);
close F;
 
for(my ($i, $j) = (0, scalar @lines); $i < $j; $i += 15)
{
	print "{";
	for my $line(@lines[$i..$i + 12])
	{
		my $int = unpack 'C', pack 'B8', $line;
		printf "0x%02X,", $int;
	}
	print "},\n";
}

Конечно, последние два скрипта можно объединить в один, но так нагляднее. Также можно заметить, что в консоли выведен не только русский алфавит. Это связано с тем, что по-умолчанию русские буквы не располагаются непрерывно в шрифте (0x80 – 0xAF и 0xE0 – 0xF1), поэтому проще захватить весь интервал (0x80 – 0xF1).

Русские символы
Русские символы

Теперь, когда у нас есть готовая таблица, нам необходимо написать код, который найдёт и перезапишет необходимый фрагмент памяти. Сначала мы должны найти bootvid.dll и адрес, по которому он загружен:

NTSTATUS InitRussian()
{
	PRTL_PROCESS_MODULE_INFORMATION minfo = NULL;
	NTSTATUS code;
	ULONG i, m_size, glyph_offset = 0, image_size = 0;
	PVOID image = NULL;
 
	/* Размер, необходимый для RTL_PROCESS_MODULE_INFORMATION */
	code = NtQuerySystemInformation(SystemModuleInformation, minfo, 0, &m_size);
	if(code != STATUS_INFO_LENGTH_MISMATCH)
	{
		return code;
	}
 
	/* Выделяем память */
	code = AllocMemory((PVOID *)&minfo, m_size);
	if(!NT_SUCCESS(code))
	{
		return code;
	}
 
	/* Заполняем структуру */
	code = NtQuerySystemInformation(SystemModuleInformation, minfo, m_size, NULL);
	if(!NT_SUCCESS(code))
	{
		FreeMemory(minfo, m_size);
		return code;
	}
 
	/* Количество элементов в структуре */
	m_size = *(PULONG)minfo;
	minfo = (PRTL_PROCESS_MODULE_INFORMATION)((PUCHAR)minfo + 4);
 
	/* Перечисляем модули */
	for(i = 0; i < m_size; i++)
	{
		if(strstr(minfo[i].FullPathName, "BOOTVID"))
		{
			image_size = minfo[i].ImageSize;
 
			/* Выделяем память под содержимое bootvid */
			code = AllocMemory(&image, image_size);
			if(!NT_SUCCESS(code))
			{
				FreeMemory(minfo, m_size);
				return code;
			}

Также нам понадобятся дополнительные функции, с помощью которых мы будем читать и писать в память:

NTSTATUS ReadVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize)
{ 
	SYSDBG_VIRTUAL MemoryChunks;
	MemoryChunks.Address = VirtualAddress;
	MemoryChunks.Buffer = Buffer;
	MemoryChunks.Request = BufferSize;
	return NtSystemDebugControl(SysDbgReadVirtual, &MemoryChunks, 
    sizeof(MemoryChunks), NULL, 0, NULL);
}
 
NTSTATUS WriteVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize)
{
	SYSDBG_VIRTUAL MemoryChunks;
	MemoryChunks.Address = VirtualAddress;
	MemoryChunks.Buffer = Buffer;
	MemoryChunks.Request = BufferSize;
	return NtSystemDebugControl(SysDbgWriteVirtual, &MemoryChunks, 
    sizeof(MemoryChunks), NULL, 0, NULL);
}

Теперь прочитаем память bootvid и найдём начало таблицы:

#define GLYPH_SIZE 13
char first_glyph[13] = 
{0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 
 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00};
/* Читаем содержимое памяти bootvid в буфер */
			code = ReadVirtualMemory(minfo[i].ImageBase, image, image_size);
			if(!NT_SUCCESS(code))
			{
				FreeMemory(minfo, m_size);
				FreeMemory(image, image_size);
				return code;
			}
			/* Ищем начало таблицы глифов */
			glyph_offset = search((char *)image, first_glyph, image_size, GLYPH_SIZE);
/* Функция поиска */
ULONG __stdcall search(char *x, char *y, unsigned int n, unsigned int m)
{
	unsigned int i;
	char first, second, *third;
	first = y[0]; 
	second = y[1]; 
	third = &y[2];
 
	for(i = 0; i < n; i++)
	{
		if(x[i] == first && x[i+1] == second)
		{
			if(RtlCompareMemory(&x[i+2], third, m - 2) == m - 2)
			{
				return i;
			}
		}
	}
 
	return 0;
}

И, наконец, переписываем часть памяти:

if (glyph_offset != 0)
			{
				/* Смещаемся на 128 глифов вперёд */
				glyph_offset += (128 * GLYPH_SIZE) + (ULONG)minfo[i].ImageBase;
				/* Записываем изменённую таблицу в память */
				return WriteVirtualMemory((PVOID)glyph_offset, 
          ru_glyph, sizeof(ru_glyph));
			}
		}
	}
 
	/* Освобождаем память */
	FreeMemory(minfo, m_size);
	FreeMemory(image, image_size);
 
	return STATUS_NOT_FOUND;
}

Таким образом, мы получили готовую функцию для добавления поддержки русского языка. Следующий код позволяет убедиться в том, что она отлично работает на XP SP3:

void __stdcall NtProcessStartup(PPEB Argument)
{
	NTSTATUS code;
 
	char str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
  "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя";
 
	zenwinx_native_init();
	winx_init(Argument);
 
	winx_printf("%s\n\n", str);
	winx_getch();
 
	code = InitRussian();
	if(code != STATUS_SUCCESS)
	{
		winx_printf("Error: 0x%x - %d\n", code, RtlNtStatusToDosError(code));
	}
 
	winx_printf("%s\n\n", str);
	winx_getch();
 
	winx_exit(0);
	return;
}

А вот как выглядит результат работы:

Русский язык в native-приложении
Русский язык в native-приложении

Однако, у этого кода есть минус – он не работает под ОС выше XP SP3 и пока непонятно, как адаптировать его под них. Исходный код проекта: скачать.



Автор: kaimi.ru
Дата: 26.01.2011


При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт. По поводу рекламы обращайтесь на почту [email protected]