Введение


В этой статье мы создадим русский шрифт и русифицируем с его помощью главную страницу настроек из имеющегося в edk2 примера.



Кого заинтересовало — прошу пожаловать под кат.

Дисклаймер
Уменьшенный по высоте русский шрифт в КДПВ — следствие того, что автор пол-дня искал битмап шрифт 8х19, из которого можно было бы взять глифы, но не нашел и взял 8х16 из терминальной консоли Win7. Если кто погружен в эту тему глубоко — дайте название fixed шрифта с битмапом 8x19, в идеале — free, я поправлю рисунки и статью.

Чтобы вывести русский язык на основной странице, надо иметь доступ к исходникам используемой прошивки. Т.е. после прохождения статьи вы сможете добавить русскую локаль в свою сборку UEFI BIOS, на виртуальной или реальной машине, а вот добавить описанным способом русский язык в существующую аппаратную систему какого-нибудь брэнда – не получится, поскольку нет доступа к исходному коду прошивки. То есть теоретически можно, но потребуются существенно большие усилия и объем знаний, чем описаны в этой статье.

Для начала немного теории. Начнем с того, что True Type Fonts в UEFI нет. По крайней мере, сейчас, в реализации UEFI Specification 2.6. Только Fixed. Шрифты состоят из так называемых glyphs – маленьких битмапов фиксированной ширины и высоты, по умолчанию – 8x19, но могут быть, разумеется, и другие размеры – например, двойной ширины для иероглифов. Вот glyph для заглавной английской буквы «A»:



Этот битмап хранится в текстовом виде в формате структуры EFI_NARROW_GLYPH:

typedef struct {
   CHAR16                 UnicodeWeight;
   UINT8                  Attributes;
   UINT8                  GlyphCol1[EFI_GLYPH_HEIGHT];
 } EFI_NARROW_GLYPH;   

Рассмотрим поля этой структуры подробнее:

UnicodeWeight — код символа в UCS-2. Про это можно прочитать, например, вот здесь — или набрав в Гугле UCS2

Attributes — показывает, какой высоты и ширины в пикселах должен быть шрифт, должен ли быть пробел после букв, одинарная или двойная ширина. На текущий момент это поле не анализируется в edk2, вместо него стоит нулевое значение в реализации системного шрифта. Можно посмотреть описание атрибутов в спецификации UEFI 2.6, п. 31.3.2.2 EFI_NARROW_GLYPH.

Русские символы имеют код 04xx, где 04 — это кодировка русского языка, а xx — код соответствующей русской буквы, от «А»(0410) до «я»(044F), именно эти коды и будут содержаться в поле UnicodeWeight.

Как говорится, лучше один раз увидеть, чем сто раз прочитать описание. Вот так выглядит глиф пробела для английской кодировки 00xx, в качестве элемента вышеописанной структуры EFI_NARROW_GLYPH:

{ 0x0020, //0x20 - код пробела, в UCS-2 и ASCII
0x00, //Атрибуты, не анализируются,
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},// битмап для пробела, все нули, что естественно

А вот так — глиф заглавной английской буквы А:

{ 0x0041, // 0x41 - код буквы “A”
0x00, //Атрибуты
{0x00,0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}},//битмап для “A”, сопоставьте с картинкой выше

Все с теорией пока что, переходим к практике и экспериментам. Заодно, как и было обещано раньше, надо достигнуть времени в 10 сек он нажатия F5 до открытия окна UEFI Shell — это уже вполне комфортно для работы.

Создание проекта в UEFI Driver Wizard


Указываем имя Workspace как C:/FW/edk2. Делаем первую и вторую страницу настроек, как в предыдущей статье, и жмем Finish. Разумеется, имя проекта надо изменить, поставим Matreshka — оно отражает тонкости реализации, скрытые от первого взгляда, через которые мы будем проходить в процессе.
Лучше привести соответствующие скриншоты (разумеется, GUID в вашем случае сгенерируется совсем другой):





Если вас все же обругали в конце, скорее всего, был неправильно указан Workspace. Считаем, что все получилось. Закройте UEFI Driver Wizard.

Создание проекта в Visual Studio


Открываем наш Solution NT32 и создаем в нем новый проект, нажав правой клавишей на Solution, затем Add->New Project и выбираем тип проекта Makefile Project. Имя проекта, как уже догадались — Matreshka:



Дальше будем без скриншотов, поскольку в первой статье это следовало объяснить детально, а в третьей — пожалуй, уже чересчур. Если что-то не будет понятно в процессе настроек проекта — лучше вернитесь в первую статью и посмотрите картинки.

Теперь открываем свойства нашего созданного проекта (правой клавишей щелкаем на проекте Matreshka и выбираем Properties.

Настройки NMake


В строке Build Command Line:

set NASM_PREFIX=C:\FW\NASMcall c:\FW\edk2\edksetup.bat --nt32
build –p Nt32Pkg/Nt32Pkg.dsc -m EducationPkg\Matreshka\Matreshka.inf

И здесь, разумеется, требуется пояснение, поскольку строка с build отличается от той, что была в первой статье. Разберем эту строку:

–p Nt32Pkg/Nt32Pkg.dsc

Это указание edk2, что надо перекомпилировать только Nt32Pkg пакет. Остальные перекомпилировать не надо, и утилите make (строго говоря, не ей, но в первом приближении пусть будет так) не стоит тратить время на то, чтобы проверять все пакеты в дереве edk2 на предмет изменения исходных файлов (а их там почти 200М, если обратили внимание, когда качали с git).

-m EducationPkg\Matreshka\Matreshka.inf 

Указание, что перекомпилируем только модуль Matreshka, не трогая остальные.

Эти два указания экономят нам время не только на просмотр изменения всех остальных файлов в дереве edk2, в этом случае еще и не создается заново имидж NVRAM, что, во-первых, занимает время, а во-вторых, стирает информацию, которую мы туда занесли в прошлом запуске. Пока нам это не требуется, но в следующей статье — потребуется обязательно.

В строке Rebuild All Command Line:
Без изменений из первой статьи. Просматриваем (и перекомпилируем при необходимости) все дерево edk2, с созданием нового имиджа NVRAM. Это потребуется нам также сегодня, поскольку правкой исходников в проекте Matreshka дело не ограничится, придется лезть в другие проекты, и PCD для этой цели не подойдет. Тем не менее, мы не будем здесь запускать clean для всего дерева, в целях экономии времени, а лучше вынесем ее в следующую опцию. Итак:

set NASM_PREFIX=C:\FW\NASMcall c:\FW\edk2\edksetup.bat --nt32
build

В строке Clean Command Line очищаем все дерево edk2:

set NASM_PREFIX=C:\FW\NASMcall c:\FW\edk2\edksetup.bat --nt32
build clean

Все с настройками NMake.

Настройки Debugging


Строка Command:

C:\FW\edk2\Build\NT32IA32\DEBUG_VS2010x86\IA32\SecMain.exe

Строка Working Directory:

C:\FW\edk2\Build\NT32IA32\DEBUG_VS2010x86\IA32\

Закончили с настройками Visual Studio.

Добавляем наш проект в Nt32Pkg


Открываем файл

C:\FW\edk2\Nt32Pkg\Nt32Pkg.dsc

И ищем в нем строчку, которую добавляли в прошлый раз:

EducationPkg/MyFirstDriver/MyFirstDriver.inf

Комментируем ее символом #, поскольку нам быстро надоест вводить каждый раз несколько символов, чтобы наш предыдущий проект MyFirstDriver дал нам возможность идти дальше без ввода любой фразы. Затем добавляем нашу строку, как, наверное, вы уже догадались:

EducationPkg\Matreshka\Matreshka.inf

Если вы не делали описанное в прошлой статье под номером 2, то строки с MyFirstDriver у вас нет, тогда добавьте строку с Matreshka после комментария

# Add new modules here

Редактируем startup.nsh для автозапуска нашего драйвера


На этот раз просто открываем файл startup.nsh прямо в Visual Studio, из каталога

C:\FW\edk2\Build\NT32IA32\DEBUG_VS2010x86\IA32

Комментируем в нем строку

# load MyFirstdriver.efi

И добавляем строку

load Matreshka.efi

Все с настройками. Компилируем проект по F5 и смотрим на автозагрузку драйвера, который пока ничего не делает. Компилируется и грузится — идем дальше, нет — правим ошибки.

Вначале исследуем текущую ситуацию


Щелкаем в Visual Studio правой клавишей на проекте Matreshka, выбираем Add->Existing Item, переходим в наш каталог

C:\FW\edk2\EducationPkg\MyFirstDriver

Выделяем мышкой все файлы и жмем на Add, добавляя их в наш проект Matreshka в Visual Studio.

Откроем файл Matreshka.c и создадим в самом низу, после всех функций, свою функцию MyTestString()

EFI_STATUS
    EFIAPI MyTestString(void){
	EFI_STATUS		Status;
	EFI_HII_FONT_PROTOCOL   *mHiiFontProtocol;
	EFI_FONT_DISPLAY_INFO	*FontInfo;
	CHAR16		TestString[] = L"Тестовая строка, Test String\n\r";
        //Выводим тестовую строку, чтобы проверить, выводится ли русский шрифт 
	Status = gST->ConOut->OutputString(gST->ConOut, TestString); 
        //и смотрим, что нам выдаст строка статуса
	DEBUG((EFI_D_INFO, "OutputString status = %r\n\r", Status));
        //Смотрим, какой шрифт установлен в системе
       //Получаем ссылку на экземпляр протокола EFI_HII_FONT_PROTOCOL
	Status = gBS->LocateProtocol (  
                //Интерфейс протокола, уже имеющийся в системе, который мы ищем
		&gEfiHiiFontProtocolGuid,   
                //Нам нужен только один интерфейс, конец списка 
		NULL,
                //присваиваем нашему указателю ссылку на найденный протокол
                (VOID **) &mHiiFontProtocol 
		);
	if (EFI_ERROR (Status)) {
                 //если нужного протокола в системе нет, то и делать дальше нечего
		return EFI_UNSUPPORTED;
		}
        //получим текст с именем найденного шрифта
	Status = mHiiFontProtocol->GetFontInfo (
		mHiiFontProtocol,
		NULL,
		NULL,
                //структура, из которой получаем текстовую информацию о шрифте
		&FontInfo, 
		NULL
		);
	DEBUG((EFI_D_INFO, "GetFontInfo status = %r, current font has '%s' name\n\r", Status, FontInfo->FontInfo.FontName));
        return EFI_SUCCESS;
}

Также добавим вызов нашей функции в конец функции MatreshkaDriverEntryPoint(), перед последним оператором return Status в этой функции:

  MyTestString();
  return Status;
}

И добавляем #include <Protocol/HiiFont.h> и описание нашей функции в начало файла Matreshka.c, сразу после

#include "Matreshka.h"

Должно получиться так:

#include "Matreshka.h"
#include <Protocol/HiiFont.h>
EFI_STATUS
    EFIAPI MyTestString(void);

Жмем F5. Обратите внимание на скорость запуска драйвера — долгожданные 10 сек. В первый раз компиляция шла долго, потому что из-за изменения в Nt32Pkg.dsc перекомпилировался весь Nt32Pkg, а сейчас мы вошли, наконец, в нормальную колею. Смотрим на вывод в Shell:



Пробелы и запятая вывелись исправно, поскольку они интернациональные, а вот с русскими буквами – увы.

Смотрим на вывод OVMF:



Видно, что с диагностикой от OutputString() каши не сваришь — «А в остальном, прекрасная маркиза, все хорошо, все хорошо», статус Success, хотя русские буквы не выведены. Надо как-то иначе (с) диагностировать.

Закомментируем строку c DEBUG, выводящую статус от OutputString(), все равно прока с нее, как с козла молока:

//DEBUG((EFI_D_INFO, "OutputString() status = %r\n\r", Status));

Теперь добавим к нашей функции пару переменных:

UINTN				Count = 0;
EFI_IMAGE_OUTPUT		*Blt = NULL;

И цикл поиска glyphs, выводимых в строке символов в используемом шрифте:

//пока не встретится терминатор строки \0
while (TestString[Count]) {
	//ищем информацию о нашем символе в используемом шрифте
	Status = mHiiFontProtocol->GetGlyph (
		//используемый протокол
		mHiiFontProtocol, 
		//проверяемый символ
		TestString[Count], 
		//остальное не интересует
		NULL,			
		&Blt,
		NULL
		);
	if (Blt != NULL) {
		FreePool (Blt);
		Blt = NULL;
	}
        //выводим UCS-2 код символа и переходим на следующий символ
	DEBUG ((EFI_D_INFO,"Current symbol code is 0x%04x\t",TestString[Count++]));
        // выводим статус, индицирующий наличие символа
	DEBUG ((EFI_D_INFO,"Status: %r\r\n", Status));
}

Этот цикл надо вставить в нашу функцию MyTestString() перед последней строкой

return EFI_SUCCESS;

И смотрим на вывод DEBUG в OVMF окне:



Другое дело, тут уже полезная диагностика. Разбираем данный вывод DEBUG.

Везде, где стоит русская кодовая страница 04xx, мы получили Warning Unknown Glyph, т.е. для символа с данным кодом нет в наличии соответствующего глифа. А с выводом английской строки и пробела, в том числе между русскими буквами – никаких проблем, они и на экран UEFI Shell вывелись. В конце то же самое сообщение об ошибке, но тут оно имеет полное право быть: какой же битмап на спецсимволы перевода строки и возврата каретки “\n\r”?

Проблему осознали – русских глифов банально нет. Теперь думаем, как ее решать. То есть как решать-то понятно, откуда-нибудь взять и добавить в систему, вопрос, откуда взять и как добавить.

Проблема, на самом деле, довольно большая. Нам нужны шрифты такого же размера, как имеющиеся в системе английские, 8х19, чтобы русские и английские буквы можно было ставить рядом друг с другом.

Свободных и несвободных шрифтов формата 8х19 в инете явно не вагон (попробуйте сами поискать), и мы идем по обычному пути: взять имеющийся и адаптировать под свои нужды.

Напишем простейшую программу в проекте типа Win32 Console в Visual Studio, в которой выведем в цикле в cmd консоль все русские буквы алфавита 16-го шрифта (нет 19-го fixed, а в cmd консоли TTF шрифты вывести нельзя). Реализацию программы оставим на ваше усмотрение, а результатом будет строка русских заглавных и обычных букв 8x16 пикселей. Дальше мы делаем скриншот выводимой строки и обрезаем его до размера 560х19 точек с добавлением пустых 3 строк в нижней позиции, и записав его в формате bmp (вот он готовый)



Теперь нам надо этот bmp-файл считать построчно в один большой массив и затем последовательно вывести маленькие двумерные массивы с получившимися глифами с помощью fprintf() в текстовый файл, в формате экземпляров структуры EFI_NARROW_GLYPH, описанного в начале статьи. Коды русских символов в русской странице UCS-2 — от 0x0410 (буква «А» русская) до 0х0455 (какая-то греческая буква, тем не менее, присутствующая в русской странице UCS-2).

Код этой программы здесь приводить не будем, поскольку парсинг bmp-файлов не по теме статьи, а объем увеличивает сильно, поэтому просто напишем результат вывода в файл (можете сами поискать в интернете исходники парсеров bmp, их там есть):

Результат
  { 0x0410, 0x00, {0x00,0x00,0x00,0x30,0x78,0xcc,0xcc,0xcc,0xfc,0xcc,0xcc,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0411, 0x00, {0x00,0x00,0x00,0xfe,0x62,0x60,0x7c,0x66,0x66,0x66,0x66,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0412, 0x00, {0x00,0x00,0x00,0xfc,0x66,0x66,0x66,0x7c,0x66,0x66,0x66,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0413, 0x00, {0x00,0x00,0x00,0xfe,0x62,0x62,0x60,0x60,0x60,0x60,0x60,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0414, 0x00, {0x00,0x00,0x00,0x3e,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0xff,0xc3,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0415, 0x00, {0x00,0x00,0x00,0xfe,0x62,0x60,0x64,0x7c,0x64,0x60,0x62,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0416, 0x00, {0x00,0x00,0x00,0x99,0xdb,0x5a,0x7e,0x3c,0x7e,0x5a,0xdb,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0417, 0x00, {0x00,0x00,0x00,0x3c,0x66,0x46,0x06,0x1c,0x06,0x46,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0418, 0x00, {0x00,0x00,0x00,0xc6,0xc6,0xce,0xde,0xfe,0xf6,0xe6,0xc6,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0419, 0x00, {0x00,0x00,0x18,0xd6,0xf6,0xce,0xde,0xfe,0xf6,0xe6,0xc6,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041a, 0x00, {0x00,0x00,0x00,0xe6,0x66,0x6c,0x6c,0x78,0x6c,0x6c,0x66,0xe6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041b, 0x00, {0x00,0x00,0x00,0x1e,0x3e,0x66,0x66,0x66,0x66,0x66,0x66,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041c, 0x00, {0x00,0x00,0x00,0xc6,0xee,0xfe,0xfe,0xd6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041d, 0x00, {0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041e, 0x00, {0x00,0x00,0x00,0x38,0x6c,0xc6,0xc6,0xc6,0xc6,0xc6,0x6c,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x041f, 0x00, {0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0420, 0x00, {0x00,0x00,0x00,0xfc,0x66,0x66,0x66,0x7c,0x60,0x60,0x60,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0421, 0x00, {0x00,0x00,0x00,0x3c,0x66,0xc6,0xc0,0xc0,0xc0,0xc6,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0422, 0x00, {0x00,0x00,0x00,0x7e,0x5a,0x18,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0423, 0x00, {0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x3e,0x06,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0424, 0x00, {0x00,0x00,0x00,0x18,0x7e,0xdb,0xdb,0xdb,0xdb,0x7e,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0425, 0x00, {0x00,0x00,0x00,0xcc,0xcc,0xcc,0x78,0x30,0x78,0xcc,0xcc,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0426, 0x00, {0x00,0x00,0x00,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xfe,0x06,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0427, 0x00, {0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0x7e,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0428, 0x00, {0x00,0x00,0x00,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0429, 0x00, {0x00,0x00,0x00,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xff,0x03,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042a, 0x00, {0x00,0x00,0x00,0xe0,0xe0,0x60,0x60,0x7c,0x66,0x66,0x66,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042b, 0x00, {0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xf6,0xde,0xde,0xde,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042c, 0x00, {0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xfc,0xc6,0xc6,0xc6,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042d, 0x00, {0x00,0x00,0x00,0x78,0xcc,0xc6,0x06,0x1e,0x06,0xc6,0xcc,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042e, 0x00, {0x00,0x00,0x00,0xce,0xdb,0xdb,0xfb,0xfb,0xdb,0xdb,0xdb,0xce,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x042f, 0x00, {0x00,0x00,0x00,0x3f,0x66,0x66,0x66,0x3e,0x36,0x66,0x66,0xe7,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0430, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x06,0x3e,0x66,0x66,0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0431, 0x00, {0x00,0x00,0x00,0x00,0x00,0x02,0x3e,0x60,0x7c,0x66,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0432, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x66,0x7c,0x66,0x66,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0433, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0434, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x36,0x36,0x36,0x36,0x7f,0x63,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0435, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x7e,0x60,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0436, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x6b,0x3e,0x3e,0x6b,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0437, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x46,0x1c,0x06,0x46,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0438, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x6e,0x7e,0x76,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0439, 0x00, {0x00,0x00,0x00,0x04,0x0c,0x08,0x66,0x66,0x6e,0x7e,0x76,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043a, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x6c,0x78,0x6c,0x64,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043b, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x1e,0x16,0x36,0x26,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043c, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x77,0x6b,0x6b,0x63,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043d, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x7e,0x66,0x66,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043e, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x043f, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0440, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x66,0x66,0x66,0x66,0x7c,0x60,0x60,0x00,0x00,0x00,0x00,0x00}},
  { 0x0441, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0442, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0443, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x1f,0x03,0x33,0x1e,0x00,0x00,0x00,0x00,0x00}},
  { 0x0444, 0x00, {0x00,0x00,0x00,0x00,0x00,0x08,0x3e,0x6b,0x6b,0x6b,0x3e,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0445, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x36,0x1c,0x1c,0x36,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0446, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x7f,0x03,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0447, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x3e,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0448, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x6b,0x6b,0x6b,0x6b,0x6b,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0449, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x6b,0x6b,0x6b,0x6b,0x6b,0x7f,0x01,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044a, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x70,0x3e,0x33,0x33,0xbe,0x80,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044b, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x63,0x7b,0x6f,0x6f,0x7b,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044c, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x7c,0x66,0x66,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044d, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x1e,0x06,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044e, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x6e,0x7b,0x7b,0x7b,0x7b,0x6e,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x044f, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x66,0x66,0x3e,0x36,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0450, 0x00, {0x00,0x00,0x6c,0xfe,0x62,0x60,0x64,0x7c,0x64,0x60,0x62,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0451, 0x00, {0x00,0x00,0x00,0x48,0x48,0x00,0x78,0xcc,0xfc,0xc0,0xcc,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0452, 0x00, {0x00,0x00,0x00,0x3c,0x66,0xc6,0xc0,0xf0,0xc0,0xc6,0x66,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0453, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x78,0xcc,0xf0,0xc0,0xcc,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0454, 0x00, {0x00,0x00,0xcc,0xcc,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0455, 0x00, {0x00,0x00,0x00,0x66,0x66,0x00,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},


Глифы шрифта мы получили. Теперь, чтобы добавить русский шрифт в UEFI BIOS, у нас есть два пути:

1. Сделать все по фэн-шуй, т.е. создать шрифт, создать font package для этого шрифта, и затем каждый раз в новом модуле, требующем русскоязычного вывода, подключать этот package. Полезное знание для тех, кто делает графические приложения на UEFI, там где нужны шрифты разного размера для заголовков, текста, сносок и прочих. Проблема в том, что потребуется это знание только при работе у IBV, где в большинстве случаев все нужные шрифты уже есть. Т.е. 99.9% вероятности, что умение создавать свой font package и добавлять его в систему вам в будущем не понадобится.

2. Добавить русские буквы, точнее, глифы с ними, в шрифт sysdefault, благо русская кодовая страница там пустует, и когда понадобится вывод на русском языке — просто писать предложение на русском языке. Или на украинском, проделав указанные ниже операции для украинского шрифта.

Угадайте, какой из двух подходов будет выбран. Правильно. Копируем полученные глифы нашего шрифта в файл, где хранятся глифы английского шрифта:

C:\FW\edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe\LaffStd.c 

в массив нашего формата:

EFI_NARROW_GLYPH  gUsStdNarrowGlyphData[] = {
//
// Unicode glyphs from 0x20 to 0x7e are the same as ASCII characters 0x20 to 0x7e
//
{ 0x0020, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  { 0x0021, 0x00, {0x00,0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}},
...

Добавим сгенерированные glyphs в этот массив, после строки

{ 0x00ff, 0x00, {0x00,0x00,0x00,0xC6,0xC6,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00}},

Чтобы не забыть потом, обозначим комментариями //Russian Symbols начало и конец нашей вставки. Собственно, на этом все – коды русских букв в поле UnicodeWeight этого массива, естественно, ни с чем не пересекаются, и ничего нигде изменять больше не надо.

Добавили, запустили Rebuild (если мы нажмем кнопку отладки F5 в Visual Studio, будет перекомпилирован только наш проект Matreshka, а GraphicsConsoleDxe, из которого и берутся glyphs, останется неперекомпилированным). Если ошибок компиляции нет, то запускаем наш модуль на отладку и смотрим на результаты: совсем другое дело!



Маленький, по сравнению с английским, русский шрифт обусловлен тем, что мы брали для копирования шрифт 16х8, а не 19х8 — такого не было. Даже 18 шрифт имеет размер 18х9, что привело бы к наложению букв друг на друга по бокам.

Смотрим диагностику: и здесь благолепие



Все символы имеют свои глифы, за исключением двух последних, возврат строки и перевод каретки, что логично. То есть все работает нормально, у нас есть теперь русский шрифт в edk2 и мы можем свободно выводить строки на русском языке. Чем сейчас и воспользуемся.

Русификация формы HII


Вначале — теория, рассказ о том, что такое HIIHuman Interface Infrastructure.

Начинать лучше с картинки из стандарта, описывающей компоненты HII системы:



Рассмотрим каждый из компонентов системы подробнее.

HID DevicesHuman Interface Devices, ими могут являться мышь, клавиатура, тачпад, трекбол и многое другое. Цель – ввести в систему информацию от человека, который использует систему, т.е. работает в UEFI BIOS.

Display Devices – наоборот, выводят информацию от UEFI BIOS к пользователю. «Display» в данном контексте — не «монитор», а «отображение». В повседневной работе не нужны (помните, когда в последний раз в BIOS заходили?). Но вот в случае реконфигурации системы и/или отладки эти устройства становятся незаменимыми, поскольку пользователю необходимо видеть реакцию системы на свои действия в процессе настройки/отладки. Поэтому информация выводится онлайн на Display Devices, которыми могут служить собственно дисплей, COM-порт, сетевое удаленное подключение – вот так, например:



EFI Global Variable Store – та самая NVRAM, флешка, в которой хранится код UEFI BIOS и заодно переменные, например – номер диска и раздела диска, с которого мы производим загрузку.

HII Database – единый репозиторий всей информации HII. База данных – она и есть база данных. Взаимодействие с ней идет не напрямую от устройств ввода-вывода, а через соответствующие драйвера этих устройств, через протоколы (вспоминаем предыдущую статью).



Что касается браузера, то лучше прибегнуть к аналогии. Forms Browser, по сути – это аналог системы клиент-сервер, где клиентом является VFR – Visual Form Representation, посредством которого происходит взаимодействие с пользователем, а сервером – IFR, Internal Form Representation, бинарный компактный код, удобный для работы машины.

На этом общеобразовательная часть закончена, переходим к конкретике. Те, кому знаний все еще мало, могут детально посмотреть описание HII в UEFI стандарте, главы 31-33. Стандарт UEFI написан понятным языком, с большими поясняющими вставками. Проблем с ним всего две — во-первых, он большой (2525 страниц в версии 2.6), во-вторых, на английском.

Добавляем поддержку русского языка на страницу


Итак, наша цель – добавить поддержку русского языка на страницу Front Page, т.е. добавить код и данные, которые позволят нам видеть отображение информации на русском языке. Поскольку Front Page создавали не мы, нам придется корректировать файлы из другого проекта.

Соответственно, нас сейчас интересует та часть, которая обозначена как Strings. Работа со стрингами происходит через String ID следующим образом:



То есть одному String ID соответствуют несколько строк на разных языках, с разными символами.

Список поддерживаемых для данного проекта языков, как и выводимые строки, хранятся в файле *.uni, где uni означает Unicode (UCS есть аббревиатура от UniCode Strings). Это выглядит следующим образом:

#langdef en "English"
#langdef es "Spanish"
#langdef cn "Chinese"

#string STR_HELLO_WORLD		#language en	"Hello World"
				#language es	"Hola Mundo"
				#language cn	"????"

Соответственно, когда мы переключаем язык на странице Front Page, система присваивает String ID “STR_HELLO_WORLD” строку на требуемом языке и она выводится в форму. Все просто.

Есть одна неочевидная проблема. Это в MS Word или на веб-странице можно поместить в один документ русские, английские и китайские символы, а Visual Studio, помимо английской, потребует выбрать какую-то одну кодовую страницу, например, русскую. Чтобы этого избежать, надо создавать файл *.uni для хранения строк в редакторе, поддерживающем UCS-2 Little Endian, например – Notepad++. Для нас это не будет иметь большого значения, поскольку мы добавим только одну локаль — русскую, и нам все равно, испортится французская локаль или нет, но надо иметь такую проблему в виду и пользоваться редакторами с поддержкой UCS-2 для файлов *.uni.

Применяем теорию к практике


Итак, приступаем. Наша задача сейчас – добавить на существующую страницу Front Page локализацию для русского языка.

Для начала посмотрим на то, что имеем сейчас. После загрузки в UEFI Shell наберем exit и попадем на Front Page:



Вот и все доступные языки локализации. Французский нам ни к чему, нам русский нужен. Можно было бы французский и выкинуть совсем, но мы будем править не в своем проекте, а в системном, тут чем меньше изменений, тем лучше, так что оставим.

Давайте вначале сократим ручной труд по набиранию exit и изменим файл startup.nsh, добавив exit в последней строчке

fs0:
load Matreshka.efi
exit

Доработаем показанную страницу Front Page, чтобы русифицировать ее. Прежде всего разберемся с тэгами языков, выводимых на этой странице приложением UiApp в файле
C:\FW\edk2\MdePkg\MdePkg.dec

Добавим в строчке с PcdUefiVariableDefaultPlatformLangCodes русский язык (ru-RU в конце)

## Default platform supported RFC 4646 languages: (American) English & French.
# @Prompt Default Value of PlatformLangCodes Variable.
gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLangCodes|"en;fr;en-US;fr-FR;ru-RU"|VOID*|0x0000001e

Ок, теперь правим файл

C:\FW\edk2\MdeModulePkg\Application\UiApp\FrontPageStrings.uni

Только вот открыть его надо в Notepad++, сразу же установить кодировку UCS-2 BE (в меню Encoding->UCS-2 BE), затем добавить поддержку русского языка #langdef ru-RU «Русский»

#langdef   en-US "English"
#langdef   fr-FR "Francais"
#langdef   en    "Standard English"
#langdef   fr    "Standard Francais"
#langdef   ru-RU "Русский"

Это еще не все, нам надо русифицировать надписи Select Language, Continue, Reset. Добавляем русские строки для соответствующих им тэгов в этом же файле FrontPageStrings.uni:

#string STR_LANGUAGE_SELECT         #language en-US  "Select Language"
                                    #language fr-FR  "Choisir la Langue"
				    #language ru-RU  "Выберите язык"

#string STR_LANGUAGE_SELECT_HELP    #language en-US  "This is the option one adjusts to change the language for the current system"
                                    #language fr-FR  "Ceci est l'option que celui ajuste changer la langue pour le systeme actuel"
				    #language ru-RU  "Здесь можно изменить текущий язык системы"

#string STR_CONTINUE_PROMPT         #language en-US  "Continue"
                                    #language fr-FR  "Continuer"
				    #language ru-RU  "Продолжить"

#string STR_RESET_STRING            #language en-US  "Reset"
                                    #language fr-FR  "Reset"
				    #language ru-RU  "Сброс"

И еще надо добавить три строки, иначе будут выводиться восклицательные знаки (для английской и французской локалей эти STRING_IDs заткнуты пробелом, мы заткнем NULL_STRING пробелом и для русской тоже, а для CPU Model и CPU Speed еще и выведем что-нибудь веселое, не пропадать же месту на форме)

#string STR_NULL_STRING                	#language en-US  " "
                                       	#language fr-FR  " "
					#language ru-RU  " "

#string STR_FRONT_PAGE_CPU_MODEL      	#language en-US  ""
                                       	#language fr-FR  ""
					#language ru-RU  "Процессор вроде Pentium"

#string STR_FRONT_PAGE_CPU_SPEED       	#language en-US  ""
                                       	#language fr-FR  ""
					#language ru-RU  "Частота около 100 ГГц"

Вот у нас и появилась опция Русский, в которой можно выбрать русский язык:



Наверное, вы уже обратили внимание, что наш драйвер Matreshka производит только тестирование языковых возможностей системы, и не более того. Самого языка он не добавляет, для этого мы работаем с уже существующими проектами UIApp из пакета MdePkg и GraphicsConsoleDxe из пакета MdeModulePkg. Теперь вы понимаете, почему нельзя добавить русскую локаль на существующей аппаратной платформе, без доступа к исходникам, как было указано в дисклаймере.

На сегодня на этом все. Создавать полностью свои страницы и русифицировать их будем в одной из следующих статей.

Комментарии (6)


  1. Nitaet
    25.09.2017 08:44
    +1

    Спасибо, давно хотел понять чем uefi shell готовить, попробую для сервера hp прикрутить русский :)


    1. DarkTiger Автор
      25.09.2017 09:04
      +1

      Не получится, если нет доступа к исходникам.

      Дело в том, что неизвестны String ID для английских выводимых строк, чтобы добавить к ним русские варианты. И даже если повесить хуки на коммуникацию с IFR, это не особо поможет, потому что тогда действительно надо добавлять русский через Font Packages.

      Можно безболезненно добавить язык через Option ROM, но только для своих новых страниц настроек, когда добавляется своя железка в компьютер. И опять же, через Font Packages.

      В общем, о русификации уже существующей системы с недоступными исходниками я как-то не подумал — у меня был в мозгу блок, что так брэнды откажутся от гарантии в случае отказа (если материнка умерла, свой модуль оттуда уже не вытащить, разве только программатором, но это вскрытие пломбы), поэтому я даже и не рассматривал такой вариант.

      Спасибо, внесу в дисклаймер.


  1. CodeRush
    25.09.2017 12:36
    +3

    Отличный материал, спасибо еще раз.
    Локализация вообще очень редко освещается в статьях о разработке ПО (просто потому, что большая часть этих статей на английском, и там локализация мало кому нужна), а тут такой подарок.


    1. DarkTiger Автор
      25.09.2017 14:50
      +2

      Случайно получилось.
      Однажды попытался вывести строку статуса на матерном русском в консоль, не смог — и как-то зацепило. Спросил в конфе edk2, почему нет локализации шрифтов, добавить же легко. Получил ответ от интеловцев, что и не планируется, из-за возможных будущих проблем с авторскими правами на шрифты. Ну что ж, мы люди гордые, просить два раза не будем :)


  1. kelevra
    27.09.2017 09:14

    Очень удивился, когда увидел. особенно при учёте того, что материнская плата официально в Россию не поставляется.


    1. DarkTiger Автор
      27.09.2017 10:47

      Ничего удивительного. BIOS не переделывается целиком под каждую новую плату, локализация была когда-то давно сделана и с тех пор не изменялась.

      Более того, даже выкинуть русскую локализацию нельзя просто по желанию — дело в том, что BIOS, как и любая другая программная система, проходит комплекс тестов. Видел я один такой документ-руководство по ручному тестированию, около 300 страниц. И если один раз тесты для русского языка в BIOS в этот комплекс тестов добавили, утвердили и все такое, то чтобы выкинуть эти тесты из комплекса, нужен обратный процесс, а он не быстрый и не простой. И инициатору выкидывания надо взять на себя ответственность, что ничего не сломается при этом. Кому нужна лишняя головная боль?

      Так что никакой загадки тут нет — скорее, было бы странно, если бы выкинули.