1 /* 2 * ReactOS kernel 3 * Copyright (C) 2004 ReactOS Team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 /* COPYRIGHT: See COPYING in the top level directory 20 * PROJECT: ReactOS text-mode setup 21 * FILE: base/setup/usetup/settings.c 22 * PURPOSE: Device settings support functions 23 * PROGRAMMERS: Colin Finck 24 */ 25 26 /* INCLUDES *****************************************************************/ 27 28 #include "precomp.h" 29 #include "genlist.h" 30 #include "infsupp.h" 31 #include "mui.h" 32 #include "registry.h" 33 34 #include "settings.h" 35 36 #define NDEBUG 37 #include <debug.h> 38 39 /* GLOBALS ******************************************************************/ 40 41 static ULONG DefaultLanguageIndex = 0; 42 43 /* FUNCTIONS ****************************************************************/ 44 45 static 46 BOOLEAN 47 IsAcpiComputer(VOID) 48 { 49 UNICODE_STRING MultiKeyPathU = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\MultifunctionAdapter"); 50 UNICODE_STRING IdentifierU = RTL_CONSTANT_STRING(L"Identifier"); 51 UNICODE_STRING AcpiBiosIdentifier = RTL_CONSTANT_STRING(L"ACPI BIOS"); 52 OBJECT_ATTRIBUTES ObjectAttributes; 53 PKEY_BASIC_INFORMATION pDeviceInformation = NULL; 54 ULONG DeviceInfoLength = sizeof(KEY_BASIC_INFORMATION) + 50 * sizeof(WCHAR); 55 PKEY_VALUE_PARTIAL_INFORMATION pValueInformation = NULL; 56 ULONG ValueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 50 * sizeof(WCHAR); 57 ULONG RequiredSize; 58 ULONG IndexDevice = 0; 59 UNICODE_STRING DeviceName, ValueName; 60 HANDLE hDevicesKey = NULL; 61 HANDLE hDeviceKey = NULL; 62 NTSTATUS Status; 63 BOOLEAN ret = FALSE; 64 65 InitializeObjectAttributes(&ObjectAttributes, 66 &MultiKeyPathU, 67 OBJ_CASE_INSENSITIVE, 68 NULL, 69 NULL); 70 Status = NtOpenKey(&hDevicesKey, 71 KEY_ENUMERATE_SUB_KEYS, 72 &ObjectAttributes); 73 if (!NT_SUCCESS(Status)) 74 { 75 DPRINT("NtOpenKey() failed with status 0x%08lx\n", Status); 76 goto cleanup; 77 } 78 79 pDeviceInformation = RtlAllocateHeap(RtlGetProcessHeap(), 0, DeviceInfoLength); 80 if (!pDeviceInformation) 81 { 82 DPRINT("RtlAllocateHeap() failed\n"); 83 Status = STATUS_NO_MEMORY; 84 goto cleanup; 85 } 86 87 pValueInformation = RtlAllocateHeap(RtlGetProcessHeap(), 0, ValueInfoLength); 88 if (!pValueInformation) 89 { 90 DPRINT("RtlAllocateHeap() failed\n"); 91 Status = STATUS_NO_MEMORY; 92 goto cleanup; 93 } 94 95 while (TRUE) 96 { 97 Status = NtEnumerateKey(hDevicesKey, 98 IndexDevice, 99 KeyBasicInformation, 100 pDeviceInformation, 101 DeviceInfoLength, 102 &RequiredSize); 103 if (Status == STATUS_NO_MORE_ENTRIES) 104 break; 105 else if (Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL) 106 { 107 RtlFreeHeap(RtlGetProcessHeap(), 0, pDeviceInformation); 108 DeviceInfoLength = RequiredSize; 109 pDeviceInformation = RtlAllocateHeap(RtlGetProcessHeap(), 0, DeviceInfoLength); 110 if (!pDeviceInformation) 111 { 112 DPRINT("RtlAllocateHeap() failed\n"); 113 Status = STATUS_NO_MEMORY; 114 goto cleanup; 115 } 116 Status = NtEnumerateKey(hDevicesKey, 117 IndexDevice, 118 KeyBasicInformation, 119 pDeviceInformation, 120 DeviceInfoLength, 121 &RequiredSize); 122 } 123 if (!NT_SUCCESS(Status)) 124 { 125 DPRINT("NtEnumerateKey() failed with status 0x%08lx\n", Status); 126 goto cleanup; 127 } 128 IndexDevice++; 129 130 /* Open device key */ 131 DeviceName.Length = DeviceName.MaximumLength = pDeviceInformation->NameLength; 132 DeviceName.Buffer = pDeviceInformation->Name; 133 InitializeObjectAttributes(&ObjectAttributes, 134 &DeviceName, 135 OBJ_CASE_INSENSITIVE, 136 hDevicesKey, 137 NULL); 138 Status = NtOpenKey(&hDeviceKey, 139 KEY_QUERY_VALUE, 140 &ObjectAttributes); 141 if (!NT_SUCCESS(Status)) 142 { 143 DPRINT("NtOpenKey() failed with status 0x%08lx\n", Status); 144 goto cleanup; 145 } 146 147 /* Read identifier */ 148 Status = NtQueryValueKey(hDeviceKey, 149 &IdentifierU, 150 KeyValuePartialInformation, 151 pValueInformation, 152 ValueInfoLength, 153 &RequiredSize); 154 if (Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL) 155 { 156 RtlFreeHeap(RtlGetProcessHeap(), 0, pValueInformation); 157 ValueInfoLength = RequiredSize; 158 pValueInformation = RtlAllocateHeap(RtlGetProcessHeap(), 0, ValueInfoLength); 159 if (!pValueInformation) 160 { 161 DPRINT("RtlAllocateHeap() failed\n"); 162 Status = STATUS_NO_MEMORY; 163 goto cleanup; 164 } 165 Status = NtQueryValueKey(hDeviceKey, 166 &IdentifierU, 167 KeyValuePartialInformation, 168 pValueInformation, 169 ValueInfoLength, 170 &RequiredSize); 171 } 172 if (!NT_SUCCESS(Status)) 173 { 174 DPRINT("NtQueryValueKey() failed with status 0x%08lx\n", Status); 175 goto nextdevice; 176 } 177 else if (pValueInformation->Type != REG_SZ) 178 { 179 DPRINT("Wrong registry type: got 0x%lx, expected 0x%lx\n", pValueInformation->Type, REG_SZ); 180 goto nextdevice; 181 } 182 183 ValueName.Length = ValueName.MaximumLength = pValueInformation->DataLength; 184 ValueName.Buffer = (PWCHAR)pValueInformation->Data; 185 if (ValueName.Length >= sizeof(WCHAR) && ValueName.Buffer[ValueName.Length / sizeof(WCHAR) - 1] == UNICODE_NULL) 186 ValueName.Length -= sizeof(WCHAR); 187 if (RtlEqualUnicodeString(&ValueName, &AcpiBiosIdentifier, FALSE)) 188 { 189 DPRINT("Found ACPI BIOS\n"); 190 ret = TRUE; 191 goto cleanup; 192 } 193 194 nextdevice: 195 NtClose(hDeviceKey); 196 hDeviceKey = NULL; 197 } 198 199 cleanup: 200 if (pDeviceInformation) 201 RtlFreeHeap(RtlGetProcessHeap(), 0, pDeviceInformation); 202 if (pValueInformation) 203 RtlFreeHeap(RtlGetProcessHeap(), 0, pValueInformation); 204 if (hDevicesKey) 205 NtClose(hDevicesKey); 206 if (hDeviceKey) 207 NtClose(hDeviceKey); 208 return ret; 209 } 210 211 static 212 BOOLEAN 213 GetComputerIdentifier( 214 OUT PWSTR Identifier, 215 IN ULONG IdentifierLength) 216 { 217 OBJECT_ATTRIBUTES ObjectAttributes; 218 UNICODE_STRING KeyName; 219 LPCWSTR ComputerIdentifier; 220 HANDLE ProcessorsKey; 221 PKEY_FULL_INFORMATION pFullInfo; 222 ULONG Size, SizeNeeded; 223 NTSTATUS Status; 224 225 DPRINT("GetComputerIdentifier() called\n"); 226 227 Size = sizeof(KEY_FULL_INFORMATION); 228 pFullInfo = (PKEY_FULL_INFORMATION)RtlAllocateHeap(RtlGetProcessHeap(), 0, Size); 229 if (!pFullInfo) 230 { 231 DPRINT("RtlAllocateHeap() failed\n"); 232 return FALSE; 233 } 234 235 /* Open the processors key */ 236 RtlInitUnicodeString(&KeyName, 237 L"\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor"); 238 InitializeObjectAttributes(&ObjectAttributes, 239 &KeyName, 240 OBJ_CASE_INSENSITIVE, 241 NULL, 242 NULL); 243 244 Status = NtOpenKey(&ProcessorsKey, 245 KEY_QUERY_VALUE, 246 &ObjectAttributes); 247 if (!NT_SUCCESS(Status)) 248 { 249 DPRINT("NtOpenKey() failed (Status 0x%lx)\n", Status); 250 RtlFreeHeap(RtlGetProcessHeap(), 0, pFullInfo); 251 return FALSE; 252 } 253 254 /* Get number of subkeys */ 255 Status = NtQueryKey(ProcessorsKey, 256 KeyFullInformation, 257 pFullInfo, 258 Size, 259 &Size); 260 NtClose(ProcessorsKey); 261 if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) 262 { 263 DPRINT("NtQueryKey() failed (Status 0x%lx)\n", Status); 264 RtlFreeHeap(RtlGetProcessHeap(), 0, pFullInfo); 265 return FALSE; 266 } 267 268 /* Find computer identifier */ 269 if (pFullInfo->SubKeys == 0) 270 { 271 /* Something strange happened. No processor detected */ 272 RtlFreeHeap(RtlGetProcessHeap(), 0, pFullInfo); 273 return FALSE; 274 } 275 276 if (IsAcpiComputer()) 277 { 278 if (pFullInfo->SubKeys == 1) 279 { 280 /* Computer is mono-CPU */ 281 ComputerIdentifier = L"ACPI UP"; 282 } 283 else 284 { 285 /* Computer is multi-CPUs */ 286 ComputerIdentifier = L"ACPI MP"; 287 } 288 } 289 else 290 { 291 if (pFullInfo->SubKeys == 1) 292 { 293 /* Computer is mono-CPU */ 294 ComputerIdentifier = L"PC UP"; 295 } 296 else 297 { 298 /* Computer is multi-CPUs */ 299 ComputerIdentifier = L"PC MP"; 300 } 301 } 302 303 RtlFreeHeap(RtlGetProcessHeap(), 0, pFullInfo); 304 305 /* Copy computer identifier to return buffer */ 306 SizeNeeded = (wcslen(ComputerIdentifier) + 1) * sizeof(WCHAR); 307 if (SizeNeeded > IdentifierLength) 308 return FALSE; 309 310 RtlCopyMemory(Identifier, ComputerIdentifier, SizeNeeded); 311 312 return TRUE; 313 } 314 315 316 /* 317 * Return values: 318 * 0x00: Failure, stop the enumeration; 319 * 0x01: Add the entry and continue the enumeration; 320 * 0x02: Skip the entry but continue the enumeration. 321 */ 322 typedef UCHAR 323 (NTAPI *PPROCESS_ENTRY_ROUTINE)( 324 IN PCWSTR KeyName, 325 IN PCWSTR KeyValue, 326 OUT PVOID* UserData, 327 OUT PBOOLEAN Current, 328 IN PVOID Parameter OPTIONAL); 329 330 static LONG 331 AddEntriesFromInfSection( 332 IN OUT PGENERIC_LIST List, 333 IN HINF InfFile, 334 IN PCWSTR SectionName, 335 IN PINFCONTEXT pContext, 336 IN PPROCESS_ENTRY_ROUTINE ProcessEntry, 337 IN PVOID Parameter OPTIONAL) 338 { 339 LONG TotalCount = 0; 340 PCWSTR KeyName; 341 PCWSTR KeyValue; 342 PVOID UserData; 343 BOOLEAN Current; 344 UCHAR RetVal; 345 346 if (!SpInfFindFirstLine(InfFile, SectionName, NULL, pContext)) 347 return -1; 348 349 do 350 { 351 /* 352 * NOTE: Do not use INF_GetData() as it expects INF entries of exactly 353 * two fields ("key = value"); however we expect to be able to deal with 354 * entries having more than two fields, the only requirement being that 355 * the second field (field number 1) contains the field description. 356 */ 357 if (!INF_GetDataField(pContext, 0, &KeyName)) 358 { 359 DPRINT("INF_GetDataField() failed\n"); 360 return -1; 361 } 362 363 if (!INF_GetDataField(pContext, 1, &KeyValue)) 364 { 365 DPRINT("INF_GetDataField() failed\n"); 366 INF_FreeData(KeyName); 367 return -1; 368 } 369 370 UserData = NULL; 371 Current = FALSE; 372 RetVal = ProcessEntry(KeyName, 373 KeyValue, 374 &UserData, 375 &Current, 376 Parameter); 377 INF_FreeData(KeyName); 378 INF_FreeData(KeyValue); 379 380 if (RetVal == 0) 381 { 382 DPRINT("ProcessEntry() failed\n"); 383 return -1; 384 } 385 else if (RetVal == 1) 386 { 387 AppendGenericListEntry(List, UserData, Current); 388 ++TotalCount; 389 } 390 // else if (RetVal == 2), skip the entry. 391 392 } while (SpInfFindNextLine(pContext, pContext)); 393 394 return TotalCount; 395 } 396 397 static UCHAR 398 NTAPI 399 DefaultProcessEntry( 400 IN PCWSTR KeyName, 401 IN PCWSTR KeyValue, 402 OUT PVOID* UserData, 403 OUT PBOOLEAN Current, 404 IN PVOID Parameter OPTIONAL) 405 { 406 PWSTR CompareKey = (PWSTR)Parameter; 407 408 PGENENTRY GenEntry; 409 SIZE_T IdSize, ValueSize; 410 411 IdSize = (wcslen(KeyName) + 1) * sizeof(WCHAR); 412 ValueSize = (wcslen(KeyValue) + 1) * sizeof(WCHAR); 413 414 GenEntry = RtlAllocateHeap(ProcessHeap, 0, 415 sizeof(*GenEntry) + IdSize + ValueSize); 416 if (GenEntry == NULL) 417 { 418 /* Failure, stop enumeration */ 419 DPRINT1("RtlAllocateHeap() failed\n"); 420 return 0; 421 } 422 423 GenEntry->Id = (PCWSTR)((ULONG_PTR)GenEntry + sizeof(*GenEntry)); 424 GenEntry->Value = (PCWSTR)((ULONG_PTR)GenEntry + sizeof(*GenEntry) + IdSize); 425 RtlStringCbCopyW((PWSTR)GenEntry->Id, IdSize, KeyName); 426 RtlStringCbCopyW((PWSTR)GenEntry->Value, ValueSize, KeyValue); 427 428 *UserData = GenEntry; 429 *Current = (CompareKey ? !_wcsicmp(KeyName, CompareKey) : FALSE); 430 431 /* Add the entry */ 432 return 1; 433 } 434 435 436 BOOLEAN 437 AddComputerTypeEntries( 438 _In_ HINF InfFile, 439 PGENERIC_LIST List, 440 _In_ PWSTR SectionName) 441 { 442 INFCONTEXT Context; 443 PCWSTR KeyName; 444 PCWSTR KeyValue; 445 WCHAR ComputerIdentifier[128]; 446 WCHAR ComputerKey[32]; 447 ULONG Count1, Count2; 448 449 /* Get the computer identification */ 450 if (!GetComputerIdentifier(ComputerIdentifier, 128)) 451 { 452 ComputerIdentifier[0] = 0; 453 } 454 455 DPRINT("Computer identifier: '%S'\n", ComputerIdentifier); 456 457 /* Search for matching device identifier */ 458 if (!SpInfFindFirstLine(InfFile, SectionName, NULL, &Context)) 459 { 460 /* FIXME: error message */ 461 return FALSE; 462 } 463 464 do 465 { 466 BOOLEAN FoundId; 467 468 if (!INF_GetDataField(&Context, 1, &KeyValue)) 469 { 470 /* FIXME: Handle error! */ 471 DPRINT("INF_GetDataField() failed\n"); 472 return FALSE; 473 } 474 475 DPRINT("KeyValue: %S\n", KeyValue); 476 FoundId = !!wcsstr(ComputerIdentifier, KeyValue); 477 INF_FreeData(KeyValue); 478 479 if (!FoundId) 480 continue; 481 482 if (!INF_GetDataField(&Context, 0, &KeyName)) 483 { 484 /* FIXME: Handle error! */ 485 DPRINT("INF_GetDataField() failed\n"); 486 return FALSE; 487 } 488 489 DPRINT("Computer key: %S\n", KeyName); 490 RtlStringCchCopyW(ComputerKey, ARRAYSIZE(ComputerKey), KeyName); 491 INF_FreeData(KeyName); 492 } while (SpInfFindNextLine(&Context, &Context)); 493 494 Count1 = AddEntriesFromInfSection(List, 495 InfFile, 496 L"Computer", 497 &Context, 498 DefaultProcessEntry, 499 ComputerKey); 500 Count2 = AddEntriesFromInfSection(List, 501 InfFile, 502 L"Computer.NT" INF_ARCH, 503 &Context, 504 DefaultProcessEntry, 505 ComputerKey); 506 if ((Count1 == -1) && (Count2 == -1)) 507 { 508 return FALSE; 509 } 510 511 return TRUE; 512 } 513 514 PGENERIC_LIST 515 CreateComputerTypeList( 516 IN HINF InfFile) 517 { 518 PGENERIC_LIST List; 519 BOOLEAN Success; 520 521 List = CreateGenericList(); 522 if (List == NULL) 523 return NULL; 524 525 Success = AddComputerTypeEntries(InfFile, List, L"Map.Computer"); 526 Success |= AddComputerTypeEntries(InfFile, List, L"Map.Computer.NT" INF_ARCH); 527 if (!Success) 528 { 529 DestroyGenericList(List, TRUE); 530 return NULL; 531 } 532 533 return List; 534 } 535 536 static 537 BOOLEAN 538 GetDisplayIdentifier( 539 OUT PWSTR Identifier, 540 IN ULONG IdentifierLength) 541 { 542 OBJECT_ATTRIBUTES ObjectAttributes; 543 UNICODE_STRING KeyName; 544 WCHAR Buffer[32]; 545 HANDLE BusKey; 546 HANDLE BusInstanceKey; 547 HANDLE ControllerKey; 548 HANDLE ControllerInstanceKey; 549 ULONG BusInstance; 550 ULONG ControllerInstance; 551 ULONG BufferLength; 552 ULONG ReturnedLength; 553 PKEY_VALUE_PARTIAL_INFORMATION ValueInfo; 554 NTSTATUS Status; 555 556 DPRINT("GetDisplayIdentifier() called\n"); 557 558 /* Open the bus key */ 559 RtlInitUnicodeString(&KeyName, 560 L"\\Registry\\Machine\\HARDWARE\\Description\\System\\MultifunctionAdapter"); 561 InitializeObjectAttributes(&ObjectAttributes, 562 &KeyName, 563 OBJ_CASE_INSENSITIVE, 564 NULL, 565 NULL); 566 567 Status = NtOpenKey(&BusKey, 568 KEY_ENUMERATE_SUB_KEYS, 569 &ObjectAttributes); 570 if (!NT_SUCCESS(Status)) 571 { 572 DPRINT("NtOpenKey() failed (Status %lx)\n", Status); 573 return FALSE; 574 } 575 576 BusInstance = 0; 577 while (TRUE) 578 { 579 RtlStringCchPrintfW(Buffer, ARRAYSIZE(Buffer), L"%lu", BusInstance); 580 RtlInitUnicodeString(&KeyName, Buffer); 581 InitializeObjectAttributes(&ObjectAttributes, 582 &KeyName, 583 OBJ_CASE_INSENSITIVE, 584 BusKey, 585 NULL); 586 587 Status = NtOpenKey(&BusInstanceKey, 588 KEY_ENUMERATE_SUB_KEYS, 589 &ObjectAttributes); 590 if (!NT_SUCCESS(Status)) 591 { 592 DPRINT("NtOpenKey() failed (Status %lx)\n", Status); 593 NtClose(BusKey); 594 return FALSE; 595 } 596 597 /* Open the controller type key */ 598 RtlInitUnicodeString(&KeyName, L"DisplayController"); 599 InitializeObjectAttributes(&ObjectAttributes, 600 &KeyName, 601 OBJ_CASE_INSENSITIVE, 602 BusInstanceKey, 603 NULL); 604 605 Status = NtOpenKey(&ControllerKey, 606 KEY_ENUMERATE_SUB_KEYS, 607 &ObjectAttributes); 608 if (NT_SUCCESS(Status)) 609 { 610 ControllerInstance = 0; 611 612 while (TRUE) 613 { 614 /* Open the pointer controller instance key */ 615 RtlStringCchPrintfW(Buffer, ARRAYSIZE(Buffer), L"%lu", ControllerInstance); 616 RtlInitUnicodeString(&KeyName, Buffer); 617 InitializeObjectAttributes(&ObjectAttributes, 618 &KeyName, 619 OBJ_CASE_INSENSITIVE, 620 ControllerKey, 621 NULL); 622 623 Status = NtOpenKey(&ControllerInstanceKey, 624 KEY_QUERY_VALUE, 625 &ObjectAttributes); 626 if (!NT_SUCCESS(Status)) 627 { 628 DPRINT("NtOpenKey() failed (Status %lx)\n", Status); 629 NtClose(ControllerKey); 630 NtClose(BusInstanceKey); 631 NtClose(BusKey); 632 return FALSE; 633 } 634 635 /* Get controller identifier */ 636 RtlInitUnicodeString(&KeyName, L"Identifier"); 637 638 BufferLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 639 256 * sizeof(WCHAR); 640 ValueInfo = (KEY_VALUE_PARTIAL_INFORMATION*) RtlAllocateHeap(RtlGetProcessHeap(), 641 0, 642 BufferLength); 643 if (ValueInfo == NULL) 644 { 645 DPRINT("RtlAllocateHeap() failed\n"); 646 NtClose(ControllerInstanceKey); 647 NtClose(ControllerKey); 648 NtClose(BusInstanceKey); 649 NtClose(BusKey); 650 return FALSE; 651 } 652 653 Status = NtQueryValueKey(ControllerInstanceKey, 654 &KeyName, 655 KeyValuePartialInformation, 656 ValueInfo, 657 BufferLength, 658 &ReturnedLength); 659 if (NT_SUCCESS(Status)) 660 { 661 DPRINT("Identifier: %S\n", (PWSTR)ValueInfo->Data); 662 663 BufferLength = min(ValueInfo->DataLength / sizeof(WCHAR), IdentifierLength); 664 RtlCopyMemory(Identifier, 665 ValueInfo->Data, 666 BufferLength * sizeof(WCHAR)); 667 Identifier[BufferLength] = 0; 668 669 RtlFreeHeap(RtlGetProcessHeap(), 670 0, 671 ValueInfo); 672 673 NtClose(ControllerInstanceKey); 674 NtClose(ControllerKey); 675 NtClose(BusInstanceKey); 676 NtClose(BusKey); 677 return TRUE; 678 } 679 680 NtClose(ControllerInstanceKey); 681 682 ControllerInstance++; 683 } 684 685 NtClose(ControllerKey); 686 } 687 688 NtClose(BusInstanceKey); 689 690 BusInstance++; 691 } 692 693 NtClose(BusKey); 694 695 return FALSE; 696 } 697 698 PGENERIC_LIST 699 CreateDisplayDriverList( 700 IN HINF InfFile) 701 { 702 PGENERIC_LIST List; 703 INFCONTEXT Context; 704 PCWSTR KeyName; 705 PCWSTR KeyValue; 706 WCHAR DisplayIdentifier[128]; 707 WCHAR DisplayKey[32]; 708 709 /* Get the display identification */ 710 if (!GetDisplayIdentifier(DisplayIdentifier, 128)) 711 { 712 DisplayIdentifier[0] = 0; 713 } 714 715 DPRINT("Display identifier: '%S'\n", DisplayIdentifier); 716 717 /* Search for matching device identifier */ 718 if (!SpInfFindFirstLine(InfFile, L"Map.Display", NULL, &Context)) 719 { 720 /* FIXME: error message */ 721 return NULL; 722 } 723 724 do 725 { 726 BOOLEAN FoundId; 727 728 if (!INF_GetDataField(&Context, 1, &KeyValue)) 729 { 730 /* FIXME: Handle error! */ 731 DPRINT("INF_GetDataField() failed\n"); 732 return NULL; 733 } 734 735 DPRINT("KeyValue: %S\n", KeyValue); 736 FoundId = !!wcsstr(DisplayIdentifier, KeyValue); 737 INF_FreeData(KeyValue); 738 739 if (!FoundId) 740 continue; 741 742 if (!INF_GetDataField(&Context, 0, &KeyName)) 743 { 744 /* FIXME: Handle error! */ 745 DPRINT("INF_GetDataField() failed\n"); 746 return NULL; 747 } 748 749 DPRINT("Display key: %S\n", KeyName); 750 RtlStringCchCopyW(DisplayKey, ARRAYSIZE(DisplayKey), KeyName); 751 INF_FreeData(KeyName); 752 } while (SpInfFindNextLine(&Context, &Context)); 753 754 List = CreateGenericList(); 755 if (List == NULL) 756 return NULL; 757 758 if (AddEntriesFromInfSection(List, 759 InfFile, 760 L"Display", 761 &Context, 762 DefaultProcessEntry, 763 DisplayKey) == -1) 764 { 765 DestroyGenericList(List, TRUE); 766 return NULL; 767 } 768 769 #if 0 770 AppendGenericListEntry(List, L"Other display driver", NULL, TRUE); 771 #endif 772 773 return List; 774 } 775 776 777 BOOLEAN 778 ProcessComputerFiles( 779 IN HINF InfFile, 780 IN PGENERIC_LIST List, 781 OUT PWSTR* AdditionalSectionName) 782 { 783 PGENERIC_LIST_ENTRY Entry; 784 static WCHAR SectionName[128]; 785 786 DPRINT("ProcessComputerFiles() called\n"); 787 788 Entry = GetCurrentListEntry(List); 789 if (Entry == NULL) 790 return FALSE; 791 792 RtlStringCchPrintfW(SectionName, ARRAYSIZE(SectionName), 793 L"Files.%s", ((PGENENTRY)GetListEntryData(Entry))->Id); 794 *AdditionalSectionName = SectionName; 795 796 return TRUE; 797 } 798 799 BOOLEAN 800 ProcessDisplayRegistry( 801 IN HINF InfFile, 802 IN PGENERIC_LIST List) 803 { 804 NTSTATUS Status; 805 PGENERIC_LIST_ENTRY Entry; 806 INFCONTEXT Context; 807 PCWSTR Buffer; 808 PCWSTR ServiceName; 809 ULONG StartValue; 810 ULONG Width, Height, Bpp; 811 OBJECT_ATTRIBUTES ObjectAttributes; 812 UNICODE_STRING KeyName; 813 HANDLE KeyHandle; 814 WCHAR RegPath[255]; 815 816 DPRINT("ProcessDisplayRegistry() called\n"); 817 818 Entry = GetCurrentListEntry(List); 819 if (Entry == NULL) 820 return FALSE; 821 822 if (!SpInfFindFirstLine(InfFile, L"Display", 823 ((PGENENTRY)GetListEntryData(Entry))->Id, 824 &Context)) 825 { 826 DPRINT1("SpInfFindFirstLine() failed\n"); 827 return FALSE; 828 } 829 830 /* Enable the correct driver */ 831 if (!INF_GetDataField(&Context, 3, &ServiceName)) 832 { 833 DPRINT1("INF_GetDataField() failed\n"); 834 return FALSE; 835 } 836 837 ASSERT(wcslen(ServiceName) < 10); 838 DPRINT("Service name: '%S'\n", ServiceName); 839 840 RtlStringCchPrintfW(RegPath, ARRAYSIZE(RegPath), 841 L"System\\CurrentControlSet\\Services\\%s", 842 ServiceName); 843 RtlInitUnicodeString(&KeyName, RegPath); 844 InitializeObjectAttributes(&ObjectAttributes, 845 &KeyName, 846 OBJ_CASE_INSENSITIVE, 847 GetRootKeyByPredefKey(HKEY_LOCAL_MACHINE, NULL), 848 NULL); 849 Status = NtOpenKey(&KeyHandle, 850 KEY_SET_VALUE, 851 &ObjectAttributes); 852 if (!NT_SUCCESS(Status)) 853 { 854 DPRINT1("NtOpenKey() failed (Status %lx)\n", Status); 855 return FALSE; 856 } 857 858 StartValue = 1; 859 Status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE, KeyHandle, 860 L"Start", 861 REG_DWORD, 862 &StartValue, 863 sizeof(StartValue)); 864 NtClose(KeyHandle); 865 if (!NT_SUCCESS(Status)) 866 { 867 DPRINT1("RtlWriteRegistryValue() failed (Status %lx)\n", Status); 868 return FALSE; 869 } 870 871 /* Set the resolution */ 872 873 if (!INF_GetDataField(&Context, 4, &Buffer)) 874 { 875 DPRINT1("INF_GetDataField() failed\n"); 876 return FALSE; 877 } 878 879 RtlStringCchPrintfW(RegPath, ARRAYSIZE(RegPath), 880 L"System\\CurrentControlSet\\Hardware Profiles\\Current\\System\\CurrentControlSet\\Services\\%s\\Device0", 881 ServiceName); 882 DPRINT("RegPath: '%S'\n", RegPath); 883 RtlInitUnicodeString(&KeyName, RegPath); 884 InitializeObjectAttributes(&ObjectAttributes, 885 &KeyName, 886 OBJ_CASE_INSENSITIVE, 887 GetRootKeyByPredefKey(HKEY_LOCAL_MACHINE, NULL), 888 NULL); 889 Status = NtOpenKey(&KeyHandle, 890 KEY_SET_VALUE, 891 &ObjectAttributes); 892 if (!NT_SUCCESS(Status)) 893 { 894 DPRINT1("NtOpenKey() failed (Status %lx)\n", Status); 895 return FALSE; 896 } 897 898 Width = wcstoul(Buffer, NULL, 10); 899 Status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE, KeyHandle, 900 L"DefaultSettings.XResolution", 901 REG_DWORD, 902 &Width, 903 sizeof(Width)); 904 if (!NT_SUCCESS(Status)) 905 { 906 DPRINT1("RtlWriteRegistryValue() failed (Status %lx)\n", Status); 907 NtClose(KeyHandle); 908 return FALSE; 909 } 910 911 if (!INF_GetDataField(&Context, 5, &Buffer)) 912 { 913 DPRINT1("INF_GetDataField() failed\n"); 914 NtClose(KeyHandle); 915 return FALSE; 916 } 917 918 Height = wcstoul(Buffer, 0, 0); 919 Status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE, KeyHandle, 920 L"DefaultSettings.YResolution", 921 REG_DWORD, 922 &Height, 923 sizeof(Height)); 924 if (!NT_SUCCESS(Status)) 925 { 926 DPRINT1("RtlWriteRegistryValue() failed (Status %lx)\n", Status); 927 NtClose(KeyHandle); 928 return FALSE; 929 } 930 931 if (!INF_GetDataField(&Context, 6, &Buffer)) 932 { 933 DPRINT1("INF_GetDataField() failed\n"); 934 NtClose(KeyHandle); 935 return FALSE; 936 } 937 938 Bpp = wcstoul(Buffer, 0, 0); 939 Status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE, KeyHandle, 940 L"DefaultSettings.BitsPerPel", 941 REG_DWORD, 942 &Bpp, 943 sizeof(Bpp)); 944 if (!NT_SUCCESS(Status)) 945 { 946 DPRINT1("RtlWriteRegistryValue() failed (Status %lx)\n", Status); 947 NtClose(KeyHandle); 948 return FALSE; 949 } 950 951 NtClose(KeyHandle); 952 953 DPRINT("ProcessDisplayRegistry() done\n"); 954 955 return TRUE; 956 } 957 958 BOOLEAN 959 ProcessLocaleRegistry( 960 IN PGENERIC_LIST List) 961 { 962 PGENERIC_LIST_ENTRY Entry; 963 PCWSTR LanguageId; 964 OBJECT_ATTRIBUTES ObjectAttributes; 965 UNICODE_STRING KeyName; 966 UNICODE_STRING ValueName; 967 968 HANDLE KeyHandle; 969 NTSTATUS Status; 970 971 Entry = GetCurrentListEntry(List); 972 if (Entry == NULL) 973 return FALSE; 974 975 LanguageId = ((PGENENTRY)GetListEntryData(Entry))->Id; 976 if (LanguageId == NULL) 977 return FALSE; 978 979 DPRINT("LanguageId: %S\n", LanguageId); 980 981 /* Open the default users locale key */ 982 RtlInitUnicodeString(&KeyName, 983 L".DEFAULT\\Control Panel\\International"); 984 985 InitializeObjectAttributes(&ObjectAttributes, 986 &KeyName, 987 OBJ_CASE_INSENSITIVE, 988 GetRootKeyByPredefKey(HKEY_USERS, NULL), 989 NULL); 990 991 Status = NtOpenKey(&KeyHandle, 992 KEY_SET_VALUE, 993 &ObjectAttributes); 994 if (!NT_SUCCESS(Status)) 995 { 996 DPRINT1("NtOpenKey() failed (Status %lx)\n", Status); 997 return FALSE; 998 } 999 1000 /* Set default user locale */ 1001 RtlInitUnicodeString(&ValueName, L"Locale"); 1002 Status = NtSetValueKey(KeyHandle, 1003 &ValueName, 1004 0, 1005 REG_SZ, 1006 (PVOID)LanguageId, 1007 (wcslen(LanguageId) + 1) * sizeof(WCHAR)); 1008 NtClose(KeyHandle); 1009 if (!NT_SUCCESS(Status)) 1010 { 1011 DPRINT1("NtSetValueKey() failed (Status %lx)\n", Status); 1012 return FALSE; 1013 } 1014 1015 /* Skip first 4 zeroes */ 1016 if (wcslen(LanguageId) >= 4) 1017 LanguageId += 4; 1018 1019 /* Open the NLS language key */ 1020 RtlInitUnicodeString(&KeyName, 1021 L"SYSTEM\\CurrentControlSet\\Control\\NLS\\Language"); 1022 1023 InitializeObjectAttributes(&ObjectAttributes, 1024 &KeyName, 1025 OBJ_CASE_INSENSITIVE, 1026 GetRootKeyByPredefKey(HKEY_LOCAL_MACHINE, NULL), 1027 NULL); 1028 1029 Status = NtOpenKey(&KeyHandle, 1030 KEY_SET_VALUE, 1031 &ObjectAttributes); 1032 if (!NT_SUCCESS(Status)) 1033 { 1034 DPRINT1("NtOpenKey() failed (Status %lx)\n", Status); 1035 return FALSE; 1036 } 1037 1038 /* Set default language */ 1039 RtlInitUnicodeString(&ValueName, L"Default"); 1040 Status = NtSetValueKey(KeyHandle, 1041 &ValueName, 1042 0, 1043 REG_SZ, 1044 (PVOID)LanguageId, 1045 (wcslen(LanguageId) + 1) * sizeof(WCHAR)); 1046 if (!NT_SUCCESS(Status)) 1047 { 1048 DPRINT1("NtSetValueKey() failed (Status %lx)\n", Status); 1049 NtClose(KeyHandle); 1050 return FALSE; 1051 } 1052 1053 /* Set install language */ 1054 RtlInitUnicodeString(&ValueName, L"InstallLanguage"); 1055 Status = NtSetValueKey(KeyHandle, 1056 &ValueName, 1057 0, 1058 REG_SZ, 1059 (PVOID)LanguageId, 1060 (wcslen(LanguageId) + 1) * sizeof(WCHAR)); 1061 NtClose(KeyHandle); 1062 if (!NT_SUCCESS(Status)) 1063 { 1064 DPRINT1("NtSetValueKey() failed (Status %lx)\n", Status); 1065 return FALSE; 1066 } 1067 1068 return TRUE; 1069 } 1070 1071 1072 PGENERIC_LIST 1073 CreateKeyboardDriverList( 1074 IN HINF InfFile) 1075 { 1076 PGENERIC_LIST List; 1077 INFCONTEXT Context; 1078 1079 List = CreateGenericList(); 1080 if (List == NULL) 1081 return NULL; 1082 1083 if (AddEntriesFromInfSection(List, 1084 InfFile, 1085 L"Keyboard", 1086 &Context, 1087 DefaultProcessEntry, 1088 NULL) == -1) 1089 { 1090 DestroyGenericList(List, TRUE); 1091 return NULL; 1092 } 1093 1094 return List; 1095 } 1096 1097 1098 ULONG 1099 GetDefaultLanguageIndex(VOID) 1100 { 1101 return DefaultLanguageIndex; 1102 } 1103 1104 typedef struct _LANG_ENTRY_PARAM 1105 { 1106 ULONG uIndex; 1107 PWCHAR DefaultLanguage; 1108 } LANG_ENTRY_PARAM, *PLANG_ENTRY_PARAM; 1109 1110 static UCHAR 1111 NTAPI 1112 ProcessLangEntry( 1113 IN PCWSTR KeyName, 1114 IN PCWSTR KeyValue, 1115 OUT PVOID* UserData, 1116 OUT PBOOLEAN Current, 1117 IN PVOID Parameter OPTIONAL) 1118 { 1119 PLANG_ENTRY_PARAM LangEntryParam = (PLANG_ENTRY_PARAM)Parameter; 1120 1121 PGENENTRY GenEntry; 1122 SIZE_T IdSize, ValueSize; 1123 1124 if (!IsLanguageAvailable(KeyName)) 1125 { 1126 /* The specified language is unavailable, skip the entry */ 1127 return 2; 1128 } 1129 1130 IdSize = (wcslen(KeyName) + 1) * sizeof(WCHAR); 1131 ValueSize = (wcslen(KeyValue) + 1) * sizeof(WCHAR); 1132 1133 GenEntry = RtlAllocateHeap(ProcessHeap, 0, 1134 sizeof(*GenEntry) + IdSize + ValueSize); 1135 if (GenEntry == NULL) 1136 { 1137 /* Failure, stop enumeration */ 1138 DPRINT1("RtlAllocateHeap() failed\n"); 1139 return 0; 1140 } 1141 1142 GenEntry->Id = (PCWSTR)((ULONG_PTR)GenEntry + sizeof(*GenEntry)); 1143 GenEntry->Value = (PCWSTR)((ULONG_PTR)GenEntry + sizeof(*GenEntry) + IdSize); 1144 RtlStringCbCopyW((PWSTR)GenEntry->Id, IdSize, KeyName); 1145 RtlStringCbCopyW((PWSTR)GenEntry->Value, ValueSize, KeyValue); 1146 1147 *UserData = GenEntry; 1148 *Current = FALSE; 1149 1150 if (!_wcsicmp(KeyName, LangEntryParam->DefaultLanguage)) 1151 DefaultLanguageIndex = LangEntryParam->uIndex; 1152 1153 LangEntryParam->uIndex++; 1154 1155 /* Add the entry */ 1156 return 1; 1157 } 1158 1159 PGENERIC_LIST 1160 CreateLanguageList( 1161 IN HINF InfFile, 1162 OUT PWSTR DefaultLanguage) 1163 { 1164 PGENERIC_LIST List; 1165 INFCONTEXT Context; 1166 PCWSTR KeyValue; 1167 1168 LANG_ENTRY_PARAM LangEntryParam; 1169 1170 LangEntryParam.uIndex = 0; 1171 LangEntryParam.DefaultLanguage = DefaultLanguage; 1172 1173 /* Get default language id */ 1174 if (!SpInfFindFirstLine(InfFile, L"NLS", L"DefaultLanguage", &Context)) 1175 return NULL; 1176 1177 if (!INF_GetData(&Context, NULL, &KeyValue)) 1178 return NULL; 1179 1180 wcscpy(DefaultLanguage, KeyValue); 1181 1182 List = CreateGenericList(); 1183 if (List == NULL) 1184 return NULL; 1185 1186 if (AddEntriesFromInfSection(List, 1187 InfFile, 1188 L"Language", 1189 &Context, 1190 ProcessLangEntry, 1191 &LangEntryParam) == -1) 1192 { 1193 DestroyGenericList(List, TRUE); 1194 return NULL; 1195 } 1196 1197 /* Only one language available, make it the default one */ 1198 if (LangEntryParam.uIndex == 1) 1199 { 1200 DefaultLanguageIndex = 0; 1201 wcscpy(DefaultLanguage, 1202 ((PGENENTRY)GetListEntryData(GetFirstListEntry(List)))->Id); 1203 } 1204 1205 return List; 1206 } 1207 1208 1209 PGENERIC_LIST 1210 CreateKeyboardLayoutList( 1211 IN HINF InfFile, 1212 IN PCWSTR LanguageId, 1213 OUT PWSTR DefaultKBLayout) 1214 { 1215 PGENERIC_LIST List; 1216 INFCONTEXT Context; 1217 PCWSTR KeyValue; 1218 const MUI_LAYOUTS* LayoutsList; 1219 ULONG uIndex = 0; 1220 1221 /* Get default layout id */ 1222 if (!SpInfFindFirstLine(InfFile, L"NLS", L"DefaultLayout", &Context)) 1223 return NULL; 1224 1225 if (!INF_GetData(&Context, NULL, &KeyValue)) 1226 return NULL; 1227 1228 wcscpy(DefaultKBLayout, KeyValue); 1229 1230 List = CreateGenericList(); 1231 if (List == NULL) 1232 return NULL; 1233 1234 LayoutsList = MUIGetLayoutsList(LanguageId); 1235 1236 do 1237 { 1238 // NOTE: See https://svn.reactos.org/svn/reactos?view=revision&revision=68354 1239 if (AddEntriesFromInfSection(List, 1240 InfFile, 1241 L"KeyboardLayout", 1242 &Context, 1243 DefaultProcessEntry, 1244 DefaultKBLayout) == -1) 1245 { 1246 DestroyGenericList(List, TRUE); 1247 return NULL; 1248 } 1249 1250 uIndex++; 1251 1252 } while (LayoutsList[uIndex].LangID != NULL); 1253 1254 /* Check whether some keyboard layouts have been found */ 1255 /* FIXME: Handle this case */ 1256 if (GetNumberOfListEntries(List) == 0) 1257 { 1258 DPRINT1("No keyboard layouts have been found\n"); 1259 DestroyGenericList(List, TRUE); 1260 return NULL; 1261 } 1262 1263 return List; 1264 } 1265 1266 1267 BOOLEAN 1268 ProcessKeyboardLayoutRegistry( 1269 IN PGENERIC_LIST List, 1270 IN PCWSTR LanguageId) 1271 { 1272 PGENERIC_LIST_ENTRY Entry; 1273 PCWSTR LayoutId; 1274 const MUI_LAYOUTS* LayoutsList; 1275 MUI_LAYOUTS NewLayoutsList[20]; 1276 ULONG uIndex; 1277 ULONG uOldPos = 0; 1278 1279 Entry = GetCurrentListEntry(List); 1280 if (Entry == NULL) 1281 return FALSE; 1282 1283 LayoutId = ((PGENENTRY)GetListEntryData(Entry))->Id; 1284 if (LayoutId == NULL) 1285 return FALSE; 1286 1287 LayoutsList = MUIGetLayoutsList(LanguageId); 1288 1289 if (_wcsicmp(LayoutsList[0].LayoutID, LayoutId) == 0) 1290 return TRUE; 1291 1292 for (uIndex = 1; LayoutsList[uIndex].LangID != NULL; uIndex++) 1293 { 1294 if (_wcsicmp(LayoutsList[uIndex].LayoutID, LayoutId) == 0) 1295 { 1296 uOldPos = uIndex; 1297 continue; 1298 } 1299 1300 NewLayoutsList[uIndex].LangID = LayoutsList[uIndex].LangID; 1301 NewLayoutsList[uIndex].LayoutID = LayoutsList[uIndex].LayoutID; 1302 } 1303 1304 NewLayoutsList[uIndex].LangID = NULL; 1305 NewLayoutsList[uIndex].LayoutID = NULL; 1306 NewLayoutsList[uOldPos].LangID = LayoutsList[0].LangID; 1307 NewLayoutsList[uOldPos].LayoutID = LayoutsList[0].LayoutID; 1308 NewLayoutsList[0].LangID = LayoutsList[uOldPos].LangID; 1309 NewLayoutsList[0].LayoutID = LayoutsList[uOldPos].LayoutID; 1310 1311 return AddKbLayoutsToRegistry(NewLayoutsList); 1312 } 1313 1314 #if 0 1315 BOOLEAN 1316 ProcessKeyboardLayoutFiles( 1317 IN PGENERIC_LIST List) 1318 { 1319 return TRUE; 1320 } 1321 #endif 1322 1323 BOOLEAN 1324 SetGeoID( 1325 IN PCWSTR Id) 1326 { 1327 NTSTATUS Status; 1328 OBJECT_ATTRIBUTES ObjectAttributes; 1329 UNICODE_STRING Name; 1330 HANDLE KeyHandle; 1331 1332 RtlInitUnicodeString(&Name, 1333 L".DEFAULT\\Control Panel\\International\\Geo"); 1334 InitializeObjectAttributes(&ObjectAttributes, 1335 &Name, 1336 OBJ_CASE_INSENSITIVE, 1337 GetRootKeyByPredefKey(HKEY_USERS, NULL), 1338 NULL); 1339 Status = NtOpenKey(&KeyHandle, 1340 KEY_SET_VALUE, 1341 &ObjectAttributes); 1342 if (!NT_SUCCESS(Status)) 1343 { 1344 DPRINT1("NtOpenKey() failed (Status %lx)\n", Status); 1345 return FALSE; 1346 } 1347 1348 RtlInitUnicodeString(&Name, L"Nation"); 1349 Status = NtSetValueKey(KeyHandle, 1350 &Name, 1351 0, 1352 REG_SZ, 1353 (PVOID)Id, 1354 (wcslen(Id) + 1) * sizeof(WCHAR)); 1355 NtClose(KeyHandle); 1356 if (!NT_SUCCESS(Status)) 1357 { 1358 DPRINT1("NtSetValueKey() failed (Status = %lx)\n", Status); 1359 return FALSE; 1360 } 1361 1362 return TRUE; 1363 } 1364 1365 1366 BOOLEAN 1367 SetDefaultPagefile( 1368 IN WCHAR Drive) 1369 { 1370 NTSTATUS Status; 1371 HANDLE KeyHandle; 1372 OBJECT_ATTRIBUTES ObjectAttributes; 1373 UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"); 1374 UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"PagingFiles"); 1375 WCHAR ValueBuffer[] = L"?:\\pagefile.sys 0 0\0"; 1376 1377 InitializeObjectAttributes(&ObjectAttributes, 1378 &KeyName, 1379 OBJ_CASE_INSENSITIVE, 1380 GetRootKeyByPredefKey(HKEY_LOCAL_MACHINE, NULL), 1381 NULL); 1382 Status = NtOpenKey(&KeyHandle, 1383 KEY_ALL_ACCESS, 1384 &ObjectAttributes); 1385 if (!NT_SUCCESS(Status)) 1386 return FALSE; 1387 1388 ValueBuffer[0] = Drive; 1389 1390 NtSetValueKey(KeyHandle, 1391 &ValueName, 1392 0, 1393 REG_MULTI_SZ, 1394 (PVOID)&ValueBuffer, 1395 sizeof(ValueBuffer)); 1396 1397 NtClose(KeyHandle); 1398 return TRUE; 1399 } 1400 1401 /* EOF */ 1402