Adding another platform language


Konstantin Aladyshev
 

Hello!

I was investigating the possibility of adding localization strings at
runtime. Is it possible to create another translation language at
runtime (UEFI Shell)?
From my understanding OVMF gets the list of supported languages from
the `PlatformLangCodes` variable.
So I've decided to create an UEFI shell application that would add
another language to this variable.
But it looks like it is not possible to modify this variable. I'm
getting `Write Protected` error.

Here is my code:
```
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
CHAR8* LanguageString;

Status = GetEfiGlobalVariable2(L"PlatformLangCodes",
(VOID**)&LanguageString, NULL);
if (EFI_ERROR(Status)) {
Print(L"Error! Can't perform GetEfiGlobalVariable2, status=%r\n", Status);
return Status;
}
Print(L"Before: %a\n", LanguageString);

CHAR8* NewLanguageString = AllocatePool(AsciiStrLen(LanguageString)
+ AsciiStrSize(";ru-RU"));
if (NewLanguageString == NULL) {
Print(L"Error! Can't allocate size for new PlatformLangCodes variable\n");
FreePool(LanguageString);
return EFI_OUT_OF_RESOURCES;
}

CopyMem(NewLanguageString, LanguageString, AsciiStrLen(LanguageString));
CopyMem(&NewLanguageString[AsciiStrLen(LanguageString)], ";ru-RU",
AsciiStrSize(";ru-RU"));

Print(L"After: %a\n", NewLanguageString);

Status = gRT->SetVariable (
L"PlatformLangCodes",
&gEfiGlobalVariableGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
AsciiStrSize(NewLanguageString),
NewLanguageString
);
if (EFI_ERROR(Status)) {
Print(L"Error! Can't set PlatformLangCodes variable, status=%r\n", Status);
}

FreePool(NewLanguageString);
FreePool(LanguageString);

return EFI_SUCCESS;
}
```

And here is the output:
```
FS0:\> HIIAddLocalization.efi
Before: en;fr;en-US;fr-FR
After: en;fr;en-US;fr-FR;ru-RU
Error! Can't set PlatformLangCodes variable, status=Write Protected
```

I'm not sure, but I guess the limitation comes from the
`AutoUpdateLangVariable` function from the
https://github.com/tianocore/edk2/blob/1bc232aae32e812341f10c9b938350cd93308eee/MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c#L1375
Am I correct?

Is it really impossible to add a new translation language at runtime?
What is the reason for blocking `PlatformLangCodes` variable from
modifying?

Best regards,
Konstantin Aladyshev


Konstantin Aladyshev
 

It looks like L"PlatformLangCodes" is blocked by a variable policy
enforced to this variable in the BdsDxe:

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/BdsDxe/BdsEntry.c
```
///
/// The read-only variables defined in UEFI Spec.
///
CHAR16 *mReadOnlyVariables[] = {
EFI_PLATFORM_LANG_CODES_VARIABLE_NAME, //
L"PlatformLangCodes" The language codes that the firmware supports
EFI_LANG_CODES_VARIABLE_NAME,
EFI_BOOT_OPTION_SUPPORT_VARIABLE_NAME,
EFI_HW_ERR_REC_SUPPORT_VARIABLE_NAME,
EFI_OS_INDICATIONS_SUPPORT_VARIABLE_NAME
};

...

// Mark the read-only variables if the Variable Lock protocol exists
//
Status = gBS->LocateProtocol(&gEdkiiVariablePolicyProtocolGuid,
NULL, (VOID**)&VariablePolicy);
DEBUG((DEBUG_INFO, "[BdsDxe] Locate Variable Policy protocol -
%r\n", Status));
if (!EFI_ERROR (Status)) {
for (Index = 0; Index < ARRAY_SIZE (mReadOnlyVariables); Index++) {
Status = RegisterBasicVariablePolicy(
VariablePolicy,
&gEfiGlobalVariableGuid,
mReadOnlyVariables[Index],
VARIABLE_POLICY_NO_MIN_SIZE,
VARIABLE_POLICY_NO_MAX_SIZE,
VARIABLE_POLICY_NO_MUST_ATTR,
VARIABLE_POLICY_NO_CANT_ATTR,
VARIABLE_POLICY_TYPE_LOCK_NOW
);
ASSERT_EFI_ERROR(Status);
}
}
```

In the end of DXE variable policy is locked in the
MdeModulePkg/Universal/Variable/RuntimeDxe/VariableDxe.c :
```
VOID
EFIAPI
OnEndOfDxe (
EFI_EVENT Event,
VOID *Context
)
{
EFI_STATUS Status;
DEBUG ((EFI_D_INFO, "[Variable]END_OF_DXE is signaled\n"));
...
Status = LockVariablePolicy ();
...
}
```

So it looks like it is not possible to disable this policy at runtime
and modify L"PlatformLangCodes". Therefore it is not possible to add
another localization language at runtime.

I think this limitation comes from the fact that UEFI specification
marks this variable as read-only:
```
The PlatformLangCodes variable contains a null- terminated ASCII
string representing the language codes that the firmware can support.
At initialization time the firmware computes the supported languages
and creates this data variable. Since the firmware creates this value
on each initialization, its contents are not stored in nonvolatile
memory. This value is considered read-only
```
But what is the reason for such a limitation? Wouldn't it be good if
there would be a possibility to add new localization languages at
runtime?

Best regards,
Konstantin Aladyshev

On Sat, Oct 30, 2021 at 2:14 PM Konstantin Aladyshev
<aladyshev22@...> wrote:

Hello!

I was investigating the possibility of adding localization strings at
runtime. Is it possible to create another translation language at
runtime (UEFI Shell)?
From my understanding OVMF gets the list of supported languages from
the `PlatformLangCodes` variable.
So I've decided to create an UEFI shell application that would add
another language to this variable.
But it looks like it is not possible to modify this variable. I'm
getting `Write Protected` error.

Here is my code:
```
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
CHAR8* LanguageString;

Status = GetEfiGlobalVariable2(L"PlatformLangCodes",
(VOID**)&LanguageString, NULL);
if (EFI_ERROR(Status)) {
Print(L"Error! Can't perform GetEfiGlobalVariable2, status=%r\n", Status);
return Status;
}
Print(L"Before: %a\n", LanguageString);

CHAR8* NewLanguageString = AllocatePool(AsciiStrLen(LanguageString)
+ AsciiStrSize(";ru-RU"));
if (NewLanguageString == NULL) {
Print(L"Error! Can't allocate size for new PlatformLangCodes variable\n");
FreePool(LanguageString);
return EFI_OUT_OF_RESOURCES;
}

CopyMem(NewLanguageString, LanguageString, AsciiStrLen(LanguageString));
CopyMem(&NewLanguageString[AsciiStrLen(LanguageString)], ";ru-RU",
AsciiStrSize(";ru-RU"));

Print(L"After: %a\n", NewLanguageString);

Status = gRT->SetVariable (
L"PlatformLangCodes",
&gEfiGlobalVariableGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
AsciiStrSize(NewLanguageString),
NewLanguageString
);
if (EFI_ERROR(Status)) {
Print(L"Error! Can't set PlatformLangCodes variable, status=%r\n", Status);
}

FreePool(NewLanguageString);
FreePool(LanguageString);

return EFI_SUCCESS;
}
```

And here is the output:
```
FS0:\> HIIAddLocalization.efi
Before: en;fr;en-US;fr-FR
After: en;fr;en-US;fr-FR;ru-RU
Error! Can't set PlatformLangCodes variable, status=Write Protected
```

I'm not sure, but I guess the limitation comes from the
`AutoUpdateLangVariable` function from the
https://github.com/tianocore/edk2/blob/1bc232aae32e812341f10c9b938350cd93308eee/MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c#L1375
Am I correct?

Is it really impossible to add a new translation language at runtime?
What is the reason for blocking `PlatformLangCodes` variable from
modifying?

Best regards,
Konstantin Aladyshev