aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_54/README.md
blob: 0be4a7484917599c208d39b36a9ce7d4ec2ebfc6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
In this lesson we would try to add UNI translations dynamically to the system.

For example we have a `PlatformDxe` which has strings only for the English language https://github.com/tianocore/edk2/blob/master/OvmfPkg/PlatformDxe/Platform.uni:
```
#langdef en-US "English"

#string STR_FORMSET_TITLE        #language en-US "OVMF Platform Configuration"
#string STR_FORMSET_HELP         #language en-US "Change various OVMF platform settings."
#string STR_MAIN_FORM_TITLE      #language en-US "OVMF Settings"
#string STR_RES_CUR              #language en-US "Preferred Resolution at Next Boot"
#string STR_RES_CUR_HELP         #language en-US "The preferred resolution of the Graphics Console at next boot. It might be unset, or even invalid (hence ignored) wrt. the video RAM size."
#string STR_RES_NEXT             #language en-US "Change Preferred Resolution for Next Boot"
#string STR_RES_NEXT_HELP        #language en-US "You can specify a new preference for the Graphics Console here. The list is filtered against the video RAM size."
#string STR_SAVE_EXIT            #language en-US "Commit Changes and Exit"
#string STR_DISCARD_EXIT         #language en-US "Discard Changes and Exit"
```

This means that even if we change our language preference to French in the BIOS menu, these strings still would be printed in English. You can verify it.

With a help of a `Select Language` option change language to `Francais`

![Translations1](Translations1.png?raw=true "Translations1")

Then look at the `Device Manager->OVMF Platform Configuration`:

![Translations2](Translations2.png?raw=true "Translations2")

As you remember one string package has translation strings only for one language. If HII element supports multiple languages it would have multiple STRING packages in its package list. As for the `PlatformDxe` it has only one string package in its package list: 
https://github.com/tianocore/edk2/blob/master/OvmfPkg/PlatformDxe/Platform.inf
```
PackageList[9]: GUID=D9DCC5DF-4007-435E-9098-8970935504B2; size=0x855
        Package[0]: type=FORMS; size=0x1F6
        Package[1]: type=STRINGS; size=0x62B
        Package[2]: type=DEVICE_PATH; size=0x1C
        Package[3]: type=END; size=0x4
```

First let's print all possible strings from the package list. We saw the UNI source, but it is better to check this, as some strings can be added dynamilly in the system.

To print strings from the package list we need `EFI_HII_HANDLE` of the package list. For this we can utilize `ListPackageLists` function from the `EFI_HII_DATABASE_PROTOCOL`:

```
EFI_HII_DATABASE_PROTOCOL.ListPackageLists()

Summary:
Determines the handles that are currently active in the database.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_HII_DATABASE_LIST_PACKS) (
 IN CONST EFI_HII_DATABASE_PROTOCOL *This,
 IN UINT8 PackageType,
 IN CONST EFI_GUID *PackageGuid,
 IN OUT UINTN *HandleBufferLength,
 OUT EFI_HII_HANDLE *Handle
 );

Parameters:
This			A pointer to the EFI_HII_DATABASE_PROTOCOL instance.
PackageType		Specifies the package type of the packages to list or EFI_HII_PACKAGE_TYPE_ALL
			for all packages to be listed.
PackageGuid		If PackageType is EFI_HII_PACKAGE_TYPE_GUID, then this is the pointer to the
			GUID which must match the Guid field of EFI_HII_GUID_PACKAGE_HDR. Otherwise, it must be NULL.
HandleBufferLength	On input, a pointer to the length of the handle buffer. On output, the length of the
			handle buffer that is required for the handles found.
Handle			An array of EFI_HII_HANDLE instances returned. Type EFI_HII_HANDLE is
			defined in EFI_HII_DATABASE_PROTOCOL.NewPackageList() in the Packages section.

Description:
This function returns a list of the package handles of the specified type that are currently active in the
database. The pseudo-type EFI_HII_PACKAGE_TYPE_ALL will cause all package handles to be listed
```

With this function we need to call it once with a `HandleBufferLength=0`, receive `EFI_BUFFER_TOO_SMALL` error, but get the value for the `HandleBufferLength`. Allocate necessary size and call this function again. Standard UEFI mechanics which is already tedious.

To make things easier let's utilize `HiiGetHiiHandles` function from the `HiiLib`:

```
/**
  Retrieves the array of all the HII Handles or the HII handles of a specific
  package list GUID in the HII Database.
  This array is terminated with a NULL HII Handle.
  This function allocates the returned array using AllocatePool().
  The caller is responsible for freeing the array with FreePool().
  @param[in]  PackageListGuid  An optional parameter that is used to request
                               HII Handles associated with a specific
                               Package List GUID.  If this parameter is NULL,
                               then all the HII Handles in the HII Database
                               are returned.  If this parameter is not NULL,
                               then zero or more HII Handles associated with
                               PackageListGuid are returned.
  @retval NULL   No HII handles were found in the HII database
  @retval NULL   The array of HII Handles could not be retrieved
  @retval Other  A pointer to the NULL terminated array of HII Handles
**/
EFI_HII_HANDLE *
EFIAPI
HiiGetHiiHandles (
  IN CONST EFI_GUID  *PackageListGuid  OPTIONAL
  )
```

With it our application can be as simple as this:
```
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include <Library/HiiLib.h>
#include <Library/MemoryAllocationLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_GUID PackageGuid = {0xD9DCC5DF, 0x4007, 0x435E, {0x90, 0x98, 0x89, 0x70, 0x93, 0x55, 0x04, 0xB2 }};
  EFI_HII_HANDLE* Handle = HiiGetHiiHandles(&PackageGuid);

  for (UINTN i=0; i<0xFFFF; i++) {
    EFI_STRING String = HiiGetString(*Handle, i, "en-US");
    if (String != NULL) {
      Print(L"ID=%d, %s\n", i, String);
    }
    FreePool(String);
  }
  return EFI_SUCCESS;
}
```
Here you can see that to get strings we simply loop over all possible string tokens and call `HiiGetString` function.

If you build and execute this application now, you would get this:
```
FS0:\> HIIAddLocalization.efi
ID=1, English
ID=2, OVMF Platform Configuration
ID=3, Change various OVMF platform settings.
ID=4, OVMF Settings
ID=5, Preferred Resolution at Next Boot
ID=6, The preferred resolution of the Graphics Console at next boot. It might be unset, or even invalid (hence ignored) wrt. the video RAM size.
ID=7, Change Preferred Resolution for Next Boot
ID=8, You can specify a new preference for the Graphics Console here. The list is filtered against the video RAM size.
ID=9, Commit Changes and Exit
ID=10, Discard Changes and Exit
ID=11, 640x480
ID=12, 800x480
ID=13, 800x600
ID=14, 832x624
ID=15, 960x640
ID=16, 1024x600
ID=17, 1024x768
ID=18, 1152x864
ID=19, 1152x870
ID=20, 1280x720
ID=21, 1280x760
ID=22, 1280x768
ID=23, 1280x800
ID=24, 1280x960
ID=25, 1280x1024
ID=26, 1360x768
ID=27, 1366x768
ID=28, 1400x1050
ID=29, 1440x900
ID=30, 1600x900
ID=31, 1600x1200
ID=32, 1680x1050
ID=33, 1920x1080
ID=34, 1920x1200
ID=35, 1920x1440
ID=36, 2000x2000
ID=37, 2048x1536
ID=38, 2048x2048
ID=39, 2560x1440
ID=40, 2560x1600
```

Let's create an array with translations to the French language:
```
  CHAR16* FrenchStrings[] = {
    L"Configuration de la OVMF plateforme",
    L"Modifier divers paramètres de la plateforme OVMF",
    L"Paramètres OVMF",
    L"Résolution préférée au prochain démarrage",
    L"La résolution préférée de la console graphique au prochain démarrage. Il peut être non défini, ou même invalide (donc ignoré) wrt. la taille de la RAM vidéo.",
    L"Modifier la résolution préférée pour le prochain démarrage",
    L"Vous pouvez spécifier ici une nouvelle préférence pour la console graphique. La liste est filtrée en fonction de la taille de la RAM vidéo.",
    L"Valider les modifications et quitter",
    L"Annuler les changements et quitter",
    L"640x480",
    L"800x480",
    L"800x600",
    L"832x624",
    L"960x640",
    L"1024x600",
    L"1024x768",
    L"1152x864",
    L"1152x870",
    L"1280x720",
    L"1280x760",
    L"1280x768",
    L"1280x800",
    L"1280x960",
    L"1280x1024",
    L"1360x768",
    L"1366x768",
    L"1400x1050",
    L"1440x900",
    L"1600x900",
    L"1600x1200",
    L"1680x1050",
    L"1920x1080",
    L"1920x1200",
    L"1920x1440",
    L"2000x2000",
    L"2048x1536",
    L"2048x2048",
    L"2560x1440",
    L"2560x1600",
  };
```
Now let's comment the strings print loop. Next we need to create a strings package and populate it to the HII database under the same Package List. Here we can use one trick. Instead of a manual string package creation we can call `gHiiString->NewString` function with the dummy `L""` string in the new language. If `en-US` string package has 40 strings, this would create `fr-FR` string package with only one string `L""` with an ID=41. And this package would be automatically added to the package list.

After the `fr-FR` string package is present in the package list, we can simply call `gHiiString->SetString` function for all of the data strings:
```
  EFI_STATUS Status;
  for (UINTN i=0; i<(sizeof(FrenchStrings)/sizeof(FrenchStrings[0])); i++) {
    EFI_STRING_ID StringId;
    if (i==0) {
      Status = gHiiString->NewString(gHiiString, *Handle, &StringId, "fr-FR", L"French", L"", NULL);
      if (EFI_ERROR(Status)) {
        Print(L"Error! NewString fail\n");
      }
    }
    StringId = i+2;
    Status = gHiiString->SetString(gHiiString, *Handle, StringId, "fr-FR", FrenchStrings[i], NULL);
    if (EFI_ERROR(Status)) {
      Print(L"Error! SetString fail for ID=%d\n", StringId);
    }
  }
```

Just in case here are the API description for the `NewString` and `SetString` functions from the `EFI_HII_STRING_PROTOCOL`:
```
EFI_HII_STRING_PROTOCOL.SetString()

Summary:
Change information about the string.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_HII_SET_STRING) (
 IN CONST EFI_HII_STRING_PROTOCOL *This,
 IN EFI_HII_HANDLE PackageList,
 IN EFI_STRING_ID StringId,
 IN CONST CHAR8 *Language,
 IN CONST EFI_STRING String,
 IN CONST EFI_FONT_INFO *StringFontInfo OPTIONAL
 );

Parameters:
This		A pointer to the EFI_HII_STRING_PROTOCOL instance.
PackageList	The package list containing the strings.
Language	Points to the language for the updated string.
StringId	The string id, which is unique within PackageList.
String		Points to the new null-terminated string.
StringFontInfo	Points to the string’s font information or NULL if the string font information is not changed.

Description:
This function updates the string specified by StringId in the specified PackageList to the text specified by
String and, optionally, the font information specified by StringFontInfo
```
```
EFI_HII_STRING_PROTOCOL.NewString()

Summary:
Creates a new string in a specific language and add it to strings from a specific package list.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_HII_NEW_STRING) (
 IN CONST EFI_HII_STRING_PROTOCOL *This,
 IN EFI_HII_HANDLE PackageList,
 OUT EFI_STRING_ID *StringId
 IN CONST CHAR8 *Language,
 IN CONST CHAR16 *LanguageName OPTIONAL,
 IN CONST EFI_STRING String,
 IN CONST EFI_FONT_INFO *StringFontInfo
 );

Parameters:
This		A pointer to the EFI_HII_STRING_PROTOCOL instance.
PackageList	Handle of the package list where this string will be added.
Language	Points to the language for the new string.
LanguageName	Points to the printable language name to associate with the passed in Language
		field.
String		Points to the new null-terminated string.
StringFontInfo	Points to the new string’s font information or NULL if the string should have the
		default system font, size and style.
StringId	On return, contains the new strings id, which is unique within PackageList.

DescriptionL
This function adds the string String to the group of strings owned by PackageList, with the specified font information StringFontInfo and returns a new string id
```

Don't forget to add `UefiHiiServicesLib` library class and its header `Library/UefiHiiServicesLib.h` for direct using of `gHiiString` protocol.

Now build and run our application now.
```
FS0:\> HIIAddLocalization.efi
FS0:\>
```
After that perform `exit` to go to the BIOS menu.

Use `Select Language` option to change language to `Francais`. Then got to the `DeviceManager->OVMF Platform Configuration`. You can already see that translation are in place:

![Translations4](Translations4.png?raw=true "Translations4")

If you exit the menu and then go to it again you could even see that even the string in the `Device manager` is changed to "Configuration de la OVMF plateforme":

![Translations3](Translations3.png?raw=true "Translations3")