/** @file Utility program to create an EFI option ROM image from binary and EFI PE32 files. Copyright (c) 1999 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "EfiUtilityMsgs.h" #include "ParseInf.h" #include "EfiRom.h" UINT64 DebugLevel = 0; int main ( int Argc, char *Argv[] ) /*++ Routine Description: Given an EFI image filename, create a ROM-able image by creating an option ROM header and PCI data structure, filling them in, and then writing the option ROM header + PCI data structure + EFI image out to the output file. Arguments: Argc - standard C main() argument count Argv - standard C main() argument list Returns: 0 success non-zero otherwise --*/ { CHAR8 *Ext; FILE *FptrOut; UINT32 Status; FILE_LIST *FList; UINT32 TotalSize; UINT32 Size; CHAR8 *Ptr0; SetUtilityName(UTILITY_NAME); Status = STATUS_SUCCESS; FptrOut = NULL; // // Parse the command line arguments // if (ParseCommandLine (Argc, Argv, &mOptions)) { return STATUS_ERROR; } if (mOptions.Quiet) { SetPrintLevel(40); } else if (mOptions.Verbose) { SetPrintLevel(15); } else if (mOptions.Debug) { SetPrintLevel(DebugLevel); } if (mOptions.Verbose) { VerboseMsg("%s tool start.\n", UTILITY_NAME); } // // If dumping an image, then do that and quit // if (mOptions.DumpOption == 1) { if (mOptions.FileList != NULL) { if ((Ptr0 = strstr ((CONST CHAR8 *) mOptions.FileList->FileName, DEFAULT_OUTPUT_EXTENSION)) != NULL) { DumpImage (mOptions.FileList); goto BailOut; } else { Error (NULL, 0, 1002, "No PciRom input file", "No *.rom input file"); goto BailOut; } } } // // Determine the output filename. Either what they specified on // the command line, or the first input filename with a different extension. // if (!mOptions.OutFileName[0]) { if (mOptions.FileList != NULL) { if (strlen (mOptions.FileList->FileName) >= MAX_PATH) { Status = STATUS_ERROR; Error (NULL, 0, 2000, "Invalid parameter", "Input file name is too long - %s.", mOptions.FileList->FileName); goto BailOut; } strncpy (mOptions.OutFileName, mOptions.FileList->FileName, MAX_PATH - 1); mOptions.OutFileName[MAX_PATH - 1] = 0; // // Find the last . on the line and replace the filename extension with // the default // Ext = mOptions.OutFileName + strlen (mOptions.OutFileName) - 1; while (Ext >= mOptions.OutFileName) { if ((*Ext == '.') || (*Ext == '\\')) { break; } Ext--; } // // If dot here, then insert extension here, otherwise append // if (*Ext != '.') { Ext = mOptions.OutFileName + strlen (mOptions.OutFileName); } strcpy (Ext, DEFAULT_OUTPUT_EXTENSION); } } // // Make sure we don't have the same filename for input and output files // for (FList = mOptions.FileList; FList != NULL; FList = FList->Next) { if (stricmp (mOptions.OutFileName, FList->FileName) == 0) { Status = STATUS_ERROR; Error (NULL, 0, 1002, "Invalid input parameter", "Input and output file names must be different - %s = %s.", FList->FileName, mOptions.OutFileName); goto BailOut; } } // // Now open our output file // if ((FptrOut = fopen (LongFilePath (mOptions.OutFileName), "wb")) == NULL) { Error (NULL, 0, 0001, "Error opening file", "Error opening file %s", mOptions.OutFileName); goto BailOut; } // // Process all our files // TotalSize = 0; for (FList = mOptions.FileList; FList != NULL; FList = FList->Next) { Size = 0; if ((FList->FileFlags & FILE_FLAG_EFI) != 0) { if (mOptions.Verbose) { VerboseMsg("Processing EFI file %s\n", FList->FileName); } Status = ProcessEfiFile (FptrOut, FList, mOptions.VendId, mOptions.DevIdList[0], &Size); } else if ((FList->FileFlags & FILE_FLAG_BINARY) !=0 ) { if (mOptions.Verbose) { VerboseMsg("Processing binary file %s\n", FList->FileName); } Status = ProcessBinFile (FptrOut, FList, &Size); } else { Error (NULL, 0, 2000, "Invalid parameter", "File type not specified, it must be either an EFI or binary file: %s.", FList->FileName); Status = STATUS_ERROR; } if (mOptions.Verbose) { VerboseMsg(" Output size = 0x%X\n", (unsigned) Size); } if (Status != STATUS_SUCCESS) { break; } TotalSize += Size; } // // Check total size // if (TotalSize > MAX_OPTION_ROM_SIZE) { Error (NULL, 0, 2000, "Invalid parameter", "Option ROM image size exceeds limit of 0x%X bytes.", MAX_OPTION_ROM_SIZE); Status = STATUS_ERROR; } BailOut: if (Status == STATUS_SUCCESS) { // // Clean up our file list // while (mOptions.FileList != NULL) { FList = mOptions.FileList->Next; free (mOptions.FileList); mOptions.FileList = FList; } // // Clean up device ID list // if (mOptions.DevIdList != NULL) { free (mOptions.DevIdList); } } if (FptrOut != NULL) { fclose (FptrOut); } if (mOptions.Verbose) { VerboseMsg("%s tool done with return code is 0x%x.\n", UTILITY_NAME, GetUtilityStatus ()); } return GetUtilityStatus (); } static int ProcessBinFile ( FILE *OutFptr, FILE_LIST *InFile, UINT32 *Size ) /*++ Routine Description: Process a binary input file. Arguments: OutFptr - file pointer to output binary ROM image file we're creating InFile - structure contains information on the binary file to process Size - pointer to where to return the size added to the output file Returns: 0 - successful --*/ { FILE *InFptr; UINT32 TotalSize; UINT32 FileSize; UINT8 *Buffer; UINT32 Status; PCI_EXPANSION_ROM_HEADER *RomHdr; PCI_DATA_STRUCTURE *PciDs23; PCI_3_0_DATA_STRUCTURE *PciDs30; UINT32 Index; UINT8 ByteCheckSum; UINT16 CodeType; PciDs23 = NULL; PciDs30 = NULL; Status = STATUS_SUCCESS; // // Try to open the input file // if ((InFptr = fopen (LongFilePath (InFile->FileName), "rb")) == NULL) { Error (NULL, 0, 0001, "Error opening file", "%s", InFile->FileName); return STATUS_ERROR; } // // Seek to the end of the input file and get the file size. Then allocate // a buffer to read it in to. // fseek (InFptr, 0, SEEK_END); FileSize = ftell (InFptr); if (mOptions.Verbose) { VerboseMsg(" File size = 0x%X\n", (unsigned) FileSize); } fseek (InFptr, 0, SEEK_SET); Buffer = (UINT8 *) malloc (FileSize); if (Buffer == NULL) { Error (NULL, 0, 4003, "Resource", "memory cannot be allocated!"); Status = STATUS_ERROR; goto BailOut; } if (fread (Buffer, FileSize, 1, InFptr) != 1) { Error (NULL, 0, 2000, "Invalid", "Failed to read all bytes from input file."); Status = STATUS_ERROR; goto BailOut; } // // Total size must be an even multiple of 512 bytes, and can't exceed // the option ROM image size. // TotalSize = FileSize; if (TotalSize & 0x1FF) { TotalSize = (TotalSize + 0x200) &~0x1ff; } if (TotalSize > MAX_OPTION_ROM_SIZE) { Error (NULL, 0, 3001, "Invalid", "Option ROM image %s size exceeds limit of 0x%X bytes.", InFile->FileName, MAX_OPTION_ROM_SIZE); Status = STATUS_ERROR; goto BailOut; } // // Return the size to the caller so they can keep track of the running total. // *Size = TotalSize; // // Crude check to make sure it's a legitimate ROM image // RomHdr = (PCI_EXPANSION_ROM_HEADER *) Buffer; if (RomHdr->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) { Error (NULL, 0, 2000, "Invalid parameter", "ROM image file has an invalid ROM signature."); Status = STATUS_ERROR; goto BailOut; } // // Make sure the pointer to the PCI data structure is within the size of the image. // Then check it for valid signature. // if ((RomHdr->PcirOffset > FileSize) || (RomHdr->PcirOffset == 0)) { Error (NULL, 0, 2000, "Invalid parameter", "Invalid PCI data structure offset."); Status = STATUS_ERROR; goto BailOut; } // // Check the header is conform to PCI2.3 or PCI3.0 // if (mOptions.Pci23 == 1) { PciDs23 = (PCI_DATA_STRUCTURE *) (Buffer + RomHdr->PcirOffset); if (PciDs23->Signature != PCI_DATA_STRUCTURE_SIGNATURE) { Error (NULL, 0, 2000, "Invalid parameter", "PCI data structure has an invalid signature."); Status = STATUS_ERROR; goto BailOut; } } else { // // Default setting is PCI3.0 header // PciDs30 = (PCI_3_0_DATA_STRUCTURE *)(Buffer + RomHdr->PcirOffset); if (PciDs30->Signature != PCI_DATA_STRUCTURE_SIGNATURE) { Error (NULL, 0, 2000, "Invalid parameter", "PCI data structure has an invalid signature."); Status = STATUS_ERROR; goto BailOut; } } // // ReSet Option Rom size // if (mOptions.Pci23 == 1) { PciDs23->ImageLength = (UINT16) (TotalSize / 512); CodeType = PciDs23->CodeType; } else { PciDs30->ImageLength = (UINT16) (TotalSize / 512); CodeType = PciDs30->CodeType; } // // If this is the last image, then set the LAST bit unless requested not // to via the command-line -n argument. Otherwise, make sure you clear it. // if ((InFile->Next == NULL) && (mOptions.NoLast == 0)) { if (mOptions.Pci23 == 1) { PciDs23->Indicator = INDICATOR_LAST; } else { PciDs30->Indicator = INDICATOR_LAST; } } else { if (mOptions.Pci23 == 1) { PciDs23->Indicator = 0; } else { PciDs30->Indicator = 0; } } if (CodeType != PCI_CODE_TYPE_EFI_IMAGE) { ByteCheckSum = 0; for (Index = 0; Index < FileSize - 1; Index++) { ByteCheckSum = (UINT8) (ByteCheckSum + Buffer[Index]); } Buffer[FileSize - 1] = (UINT8) ((~ByteCheckSum) + 1); if (mOptions.Verbose) { VerboseMsg(" Checksum = %02x\n\n", Buffer[FileSize - 1]); } } // // Now copy the input file contents out to the output file // if (fwrite (Buffer, FileSize, 1, OutFptr) != 1) { Error (NULL, 0, 0005, "Failed to write all file bytes to output file.", NULL); Status = STATUS_ERROR; goto BailOut; } TotalSize -= FileSize; // // Pad the rest of the image to make it a multiple of 512 bytes // while (TotalSize > 0) { putc (~0, OutFptr); TotalSize--; } BailOut: if (InFptr != NULL) { fclose (InFptr); } if (Buffer != NULL) { free (Buffer); } // // Print the file name if errors occurred // if (Status != STATUS_SUCCESS) { Error (NULL, 0, 0003, "Error", "Error parsing file: %s", InFile->FileName); } return Status; } static int ProcessEfiFile ( FILE *OutFptr, FILE_LIST *InFile, UINT16 VendId, UINT16 DevId, UINT32 *Size ) /*++ Routine Description: Process a PE32 EFI file. Arguments: OutFptr - file pointer to output binary ROM image file we're creating InFile - structure contains information on the PE32 file to process VendId - vendor ID as required in the option ROM header DevId - device ID as required in the option ROM header Size - pointer to where to return the size added to the output file Returns: 0 - successful --*/ { UINT32 Status; FILE *InFptr; EFI_PCI_EXPANSION_ROM_HEADER RomHdr; PCI_DATA_STRUCTURE PciDs23; PCI_3_0_DATA_STRUCTURE PciDs30; UINT32 FileSize; UINT32 CompressedFileSize; UINT8 *Buffer; UINT8 *CompressedBuffer; UINT8 *TempBufferPtr; UINT32 TotalSize; UINT32 HeaderSize; UINT16 MachineType; UINT16 SubSystem; UINT32 HeaderPadBytes; UINT32 PadBytesBeforeImage; UINT32 PadBytesAfterImage; UINT32 DevIdListSize; // // Try to open the input file // if ((InFptr = fopen (LongFilePath (InFile->FileName), "rb")) == NULL) { Error (NULL, 0, 0001, "Open file error", "Error opening file: %s", InFile->FileName); return STATUS_ERROR; } // // Initialize our buffer pointers to null. // Buffer = NULL; CompressedBuffer = NULL; // // Double-check the file to make sure it's what we expect it to be // Status = CheckPE32File (InFptr, &MachineType, &SubSystem); if (Status != STATUS_SUCCESS) { goto BailOut; } // // Seek to the end of the input file and get the file size // fseek (InFptr, 0, SEEK_END); FileSize = ftell (InFptr); // // Get the size of the headers we're going to put in front of the image. The // EFI header must be aligned on a 4-byte boundary, so pad accordingly. // if (sizeof (RomHdr) & 0x03) { HeaderPadBytes = 4 - (sizeof (RomHdr) & 0x03); } else { HeaderPadBytes = 0; } // // For Pci3.0 to use the different data structure. // if (mOptions.Pci23 == 1) { HeaderSize = sizeof (PCI_DATA_STRUCTURE) + HeaderPadBytes + sizeof (EFI_PCI_EXPANSION_ROM_HEADER); } else { if (mOptions.DevIdCount > 1) { // // Write device ID list when more than one device ID is specified. // Leave space for list plus terminator. // DevIdListSize = (mOptions.DevIdCount + 1) * sizeof (UINT16); } else { DevIdListSize = 0; } HeaderSize = sizeof (PCI_3_0_DATA_STRUCTURE) + HeaderPadBytes + DevIdListSize + sizeof (EFI_PCI_EXPANSION_ROM_HEADER); } if (mOptions.Verbose) { VerboseMsg(" File size = 0x%X\n", (unsigned) FileSize); } // // Allocate memory for the entire file (in case we have to compress), then // seek back to the beginning of the file and read it into our buffer. // Buffer = (UINT8 *) malloc (FileSize); if (Buffer == NULL) { Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!"); Status = STATUS_ERROR; goto BailOut; } fseek (InFptr, 0, SEEK_SET); if (fread (Buffer, FileSize, 1, InFptr) != 1) { Error (NULL, 0, 0004, "Error reading file", "File %s", InFile->FileName); Status = STATUS_ERROR; goto BailOut; } // // Now determine the size of the final output file. It's either the header size // plus the file's size, or the header size plus the compressed file size. // if ((InFile->FileFlags & FILE_FLAG_COMPRESS) != 0) { // // Allocate a buffer into which we can compress the image, compress it, // and use that size as the new size. // CompressedBuffer = (UINT8 *) malloc (FileSize); if (CompressedBuffer == NULL) { Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!"); Status = STATUS_ERROR; goto BailOut; } CompressedFileSize = FileSize; Status = EfiCompress (Buffer, FileSize, CompressedBuffer, &CompressedFileSize); if (Status != STATUS_SUCCESS) { Error (NULL, 0, 0007, "Error compressing file!", NULL); goto BailOut; } // // Now compute the size, then swap buffer pointers. // if (mOptions.Verbose) { VerboseMsg(" Comp size = 0x%X\n", (unsigned) CompressedFileSize); } TotalSize = CompressedFileSize + HeaderSize; FileSize = CompressedFileSize; TempBufferPtr = Buffer; Buffer = CompressedBuffer; CompressedBuffer = TempBufferPtr; } else { TotalSize = FileSize + HeaderSize; } // // Total size must be an even multiple of 512 bytes // if (TotalSize & 0x1FF) { TotalSize = (TotalSize + 0x200) &~0x1ff; } // // Workaround: // If compressed, put the pad bytes after the image, // else put the pad bytes before the image. // if ((InFile->FileFlags & FILE_FLAG_COMPRESS) != 0) { PadBytesBeforeImage = 0; PadBytesAfterImage = TotalSize - (FileSize + HeaderSize); } else { PadBytesBeforeImage = TotalSize - (FileSize + HeaderSize); PadBytesAfterImage = 0; } // // Check size // if (TotalSize > MAX_OPTION_ROM_SIZE) { Error (NULL, 0, 2000, "Invalid", "Option ROM image %s size exceeds limit of 0x%X bytes.", InFile->FileName, MAX_OPTION_ROM_SIZE); Status = STATUS_ERROR; goto BailOut; } // // Return the size to the caller so they can keep track of the running total. // *Size = TotalSize; // // Now fill in the ROM header. These values come from chapter 18 of the // EFI 1.02 specification. // memset (&RomHdr, 0, sizeof (RomHdr)); RomHdr.Signature = PCI_EXPANSION_ROM_HEADER_SIGNATURE; RomHdr.InitializationSize = (UINT16) (TotalSize / 512); RomHdr.EfiSignature = EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE; RomHdr.EfiSubsystem = SubSystem; RomHdr.EfiMachineType = MachineType; RomHdr.EfiImageHeaderOffset = (UINT16) (HeaderSize + PadBytesBeforeImage); RomHdr.PcirOffset = (UINT16) (sizeof (RomHdr) + HeaderPadBytes); // // Set image as compressed or not // if (InFile->FileFlags & FILE_FLAG_COMPRESS) { RomHdr.CompressionType = EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED; } // // Fill in the PCI data structure // if (mOptions.Pci23 == 1) { memset (&PciDs23, 0, sizeof (PCI_DATA_STRUCTURE)); } else { memset (&PciDs30, 0, sizeof (PCI_3_0_DATA_STRUCTURE)); } if (mOptions.Pci23 == 1) { PciDs23.Signature = PCI_DATA_STRUCTURE_SIGNATURE; PciDs23.VendorId = VendId; PciDs23.DeviceId = DevId; PciDs23.Length = (UINT16) sizeof (PCI_DATA_STRUCTURE); PciDs23.Revision = 0; // // Class code and code revision from the command line (optional) // PciDs23.ClassCode[0] = (UINT8) InFile->ClassCode; PciDs23.ClassCode[1] = (UINT8) (InFile->ClassCode >> 8); PciDs23.ClassCode[2] = (UINT8) (InFile->ClassCode >> 16); PciDs23.ImageLength = RomHdr.InitializationSize; PciDs23.CodeRevision = InFile->CodeRevision; PciDs23.CodeType = PCI_CODE_TYPE_EFI_IMAGE; } else { PciDs30.Signature = PCI_DATA_STRUCTURE_SIGNATURE; PciDs30.VendorId = VendId; PciDs30.DeviceId = DevId; if (mOptions.DevIdCount > 1) { // // Place device list immediately after PCI structure // PciDs30.DeviceListOffset = (UINT16) sizeof (PCI_3_0_DATA_STRUCTURE); } else { PciDs30.DeviceListOffset = 0; } PciDs30.Length = (UINT16) sizeof (PCI_3_0_DATA_STRUCTURE); PciDs30.Revision = 0x3; // // Class code and code revision from the command line (optional) // PciDs30.ClassCode[0] = (UINT8) InFile->ClassCode; PciDs30.ClassCode[1] = (UINT8) (InFile->ClassCode >> 8); PciDs30.ClassCode[2] = (UINT8) (InFile->ClassCode >> 16); PciDs30.ImageLength = RomHdr.InitializationSize; PciDs30.CodeRevision = InFile->CodeRevision; PciDs30.CodeType = PCI_CODE_TYPE_EFI_IMAGE; PciDs30.MaxRuntimeImageLength = 0; // to be fixed PciDs30.ConfigUtilityCodeHeaderOffset = 0; // to be fixed PciDs30.DMTFCLPEntryPointOffset = 0; // to be fixed } // // If this is the last image, then set the LAST bit unless requested not // to via the command-line -n argument. // if ((InFile->Next == NULL) && (mOptions.NoLast == 0)) { if (mOptions.Pci23 == 1) { PciDs23.Indicator = INDICATOR_LAST; } else { PciDs30.Indicator = INDICATOR_LAST;} } else { if (mOptions.Pci23 == 1) { PciDs23.Indicator = 0; } else { PciDs30.Indicator = 0; } } // // Write the ROM header to the output file // if (fwrite (&RomHdr, sizeof (RomHdr), 1, OutFptr) != 1) { Error (NULL, 0, 0002, "Failed to write ROM header to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } // // Write pad bytes to align the PciDs // while (HeaderPadBytes > 0) { if (putc (0, OutFptr) == EOF) { Error (NULL, 0, 0002, "Failed to write ROM header pad bytes to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } HeaderPadBytes--; } // // Write the PCI data structure header to the output file // if (mOptions.Pci23 == 1) { if (fwrite (&PciDs23, sizeof (PciDs23), 1, OutFptr) != 1) { Error (NULL, 0, 0002, "Failed to write PCI ROM header to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } } else { if (fwrite (&PciDs30, sizeof (PciDs30), 1, OutFptr) != 1) { Error (NULL, 0, 0002, "Failed to write PCI ROM header to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } } // // Write the Device ID list to the output file // if (mOptions.DevIdCount > 1) { if (fwrite (mOptions.DevIdList, sizeof (UINT16), mOptions.DevIdCount, OutFptr) != mOptions.DevIdCount) { Error (NULL, 0, 0002, "Failed to write PCI device list to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } // // Write two-byte terminating 0 at the end of the device list // if (putc (0, OutFptr) == EOF || putc (0, OutFptr) == EOF) { Error (NULL, 0, 0002, "Failed to write PCI device list to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } } // // Pad head to make it a multiple of 512 bytes // while (PadBytesBeforeImage > 0) { if (putc (~0, OutFptr) == EOF) { Error (NULL, 0, 2000, "Failed to write trailing pad bytes output file!", NULL); Status = STATUS_ERROR; goto BailOut; } PadBytesBeforeImage--; } // // Now dump the input file's contents to the output file // if (fwrite (Buffer, FileSize, 1, OutFptr) != 1) { Error (NULL, 0, 0002, "Failed to write all file bytes to output file!", NULL); Status = STATUS_ERROR; goto BailOut; } // // Pad the rest of the image to make it a multiple of 512 bytes // while (PadBytesAfterImage > 0) { if (putc (~0, OutFptr) == EOF) { Error (NULL, 0, 2000, "Failed to write trailing pad bytes output file!", NULL); Status = STATUS_ERROR; goto BailOut; } PadBytesAfterImage--; } BailOut: if (InFptr != NULL) { fclose (InFptr); } // // Free up our buffers // if (Buffer != NULL) { free (Buffer); } if (CompressedBuffer != NULL) { free (CompressedBuffer); } // // Print the file name if errors occurred // if (Status != STATUS_SUCCESS) { Error (NULL, 0, 0003, "Error parsing", "Error parsing file: %s", InFile->FileName); } return Status; } static int CheckPE32File ( FILE *Fptr, UINT16 *MachineType, UINT16 *SubSystem ) /*++ Routine Description: Given a file pointer to a supposed PE32 image file, verify that it is indeed a PE32 image file, and then return the machine type in the supplied pointer. Arguments: Fptr File pointer to the already-opened PE32 file MachineType Location to stuff the machine type of the PE32 file. This is needed because the image may be Itanium-based, IA32, or EBC. Returns: 0 success non-zero otherwise --*/ { EFI_IMAGE_DOS_HEADER DosHeader; EFI_IMAGE_OPTIONAL_HEADER_UNION PeHdr; // // Position to the start of the file // fseek (Fptr, 0, SEEK_SET); // // Read the DOS header // if (fread (&DosHeader, sizeof (DosHeader), 1, Fptr) != 1) { Error (NULL, 0, 0004, "Failed to read the DOS stub from the input file!", NULL); return STATUS_ERROR; } // // Check the magic number (0x5A4D) // if (DosHeader.e_magic != EFI_IMAGE_DOS_SIGNATURE) { Error (NULL, 0, 2000, "Invalid parameter", "Input file does not appear to be a PE32 image (magic number)!"); return STATUS_ERROR; } // // Position into the file and check the PE signature // fseek (Fptr, (long) DosHeader.e_lfanew, SEEK_SET); // // Read PE headers // if (fread (&PeHdr, sizeof (PeHdr), 1, Fptr) != 1) { Error (NULL, 0, 0004, "Failed to read PE/COFF headers from input file!", NULL); return STATUS_ERROR; } // // Check the PE signature in the header "PE\0\0" // if (PeHdr.Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) { Error (NULL, 0, 2000, "Invalid parameter", "Input file does not appear to be a PE32 image (signature)!"); return STATUS_ERROR; } memcpy ((char *) MachineType, &PeHdr.Pe32.FileHeader.Machine, 2); if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { *SubSystem = PeHdr.Pe32.OptionalHeader.Subsystem; } else if (PeHdr.Pe32Plus.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { *SubSystem = PeHdr.Pe32Plus.OptionalHeader.Subsystem; } else { Error (NULL, 0, 2000, "Invalid parameter", "Unable to find subsystem type!"); return STATUS_ERROR; } if (mOptions.Verbose) { VerboseMsg(" Got subsystem = 0x%X from image\n", *SubSystem); } // // File was successfully identified as a PE32 // return STATUS_SUCCESS; } static int ParseCommandLine ( int Argc, char *Argv[], OPTIONS *Options ) /*++ Routine Description: Given the Argc/Argv program arguments, and a pointer to an options structure, parse the command-line options and check their validity. Arguments: Argc - standard C main() argument count Argv[] - standard C main() argument list Options - pointer to a structure to store the options in Returns: STATUS_SUCCESS success non-zero otherwise --*/ { FILE_LIST *FileList; FILE_LIST *PrevFileList; UINT32 FileFlags; UINT32 ClassCode; UINT32 CodeRevision; EFI_STATUS Status; INTN ReturnStatus; BOOLEAN EfiRomFlag; UINT64 TempValue; char *OptionName; UINT16 *DevIdList; ReturnStatus = 0; FileFlags = 0; EfiRomFlag = FALSE; // // Clear out the options // memset ((char *) Options, 0, sizeof (OPTIONS)); // // To avoid compile warnings // FileList = PrevFileList = NULL; Options->DevIdList = NULL; Options->DevIdCount = 0; ClassCode = 0; CodeRevision = 0; // // Skip over the program name // Argc--; Argv++; // // If no arguments, assume they want usage info // if (Argc == 0) { Usage (); return STATUS_ERROR; } if ((stricmp(Argv[0], "-h") == 0) || (stricmp(Argv[0], "--help") == 0)) { Usage(); return STATUS_ERROR; } if ((stricmp(Argv[0], "--version") == 0)) { Version(); return STATUS_ERROR; } // // Process until no more arguments // while (Argc > 0) { if (Argv[0][0] == '-') { // // Vendor ID specified with -f // if (stricmp (Argv[0], "-f") == 0) { // // Make sure there's another parameter // Status = AsciiStringToUint64(Argv[1], FALSE, &TempValue); if (EFI_ERROR (Status)) { Error (NULL, 0, 2000, "Invalid option value", "%s = %s", Argv[0], Argv[1]); ReturnStatus = 1; goto Done; } if (TempValue >= 0x10000) { Error (NULL, 0, 2000, "Invalid option value", "Vendor Id %s out of range!", Argv[1]); ReturnStatus = 1; goto Done; } Options->VendId = (UINT16) TempValue; Options->VendIdValid = 1; Argv++; Argc--; } else if (stricmp (Argv[0], "-i") == 0) { OptionName = Argv[0]; // // Device IDs specified with -i // Make sure there's at least one more parameter // if (Argc == 1) { Error (NULL, 0, 2000, "Invalid parameter", "Missing Device Id with %s option!", OptionName); ReturnStatus = 1; goto Done; } // // Process until another dash-argument parameter or the end of the list // while (Argc > 1 && Argv[1][0] != '-') { Status = AsciiStringToUint64(Argv[1], FALSE, &TempValue); if (EFI_ERROR (Status)) { Error (NULL, 0, 2000, "Invalid option value", "%s = %s", OptionName, Argv[1]); ReturnStatus = 1; goto Done; } // // Don't allow device IDs greater than 16 bits // Don't allow 0, since it is used as a list terminator // if (TempValue >= 0x10000 || TempValue == 0) { Error (NULL, 0, 2000, "Invalid option value", "Device Id %s out of range!", Argv[1]); ReturnStatus = 1; goto Done; } DevIdList = (UINT16*) realloc (Options->DevIdList, (Options->DevIdCount + 1) * sizeof (UINT16)); if (DevIdList == NULL) { Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!", NULL); ReturnStatus = 1; goto Done; } Options->DevIdList = DevIdList; Options->DevIdList[Options->DevIdCount++] = (UINT16) TempValue; Argv++; Argc--; } } else if ((stricmp (Argv[0], "-o") == 0) || (stricmp (Argv[0], "--output") == 0)) { // // Output filename specified with -o // Make sure there's another parameter // if (Argv[1] == NULL || Argv[1][0] == '-') { Error (NULL, 0, 2000, "Invalid parameter", "Missing output file name with %s option!", Argv[0]); ReturnStatus = STATUS_ERROR; goto Done; } if (strlen (Argv[1]) > MAX_PATH - 1) { Error (NULL, 0, 2000, "Invalid parameter", "Output file name %s is too long!", Argv[1]); ReturnStatus = STATUS_ERROR; goto Done; } strncpy (Options->OutFileName, Argv[1], MAX_PATH - 1); Options->OutFileName[MAX_PATH - 1] = 0; Argv++; Argc--; } else if ((stricmp (Argv[0], "-h") == 0) || (stricmp (Argv[0], "--help") == 0)) { // // Help option // Usage (); ReturnStatus = STATUS_ERROR; goto Done; } else if (stricmp (Argv[0], "-b") == 0) { // // Specify binary files with -b // FileFlags = FILE_FLAG_BINARY; } else if ((stricmp (Argv[0], "-e") == 0) || (stricmp (Argv[0], "-ec") == 0)) { // // Specify EFI files with -e. Specify EFI-compressed with -c. // FileFlags = FILE_FLAG_EFI; if ((Argv[0][2] == 'c') || (Argv[0][2] == 'C')) { FileFlags |= FILE_FLAG_COMPRESS; } // // Specify not to set the LAST bit in the last file with -n // } else if (stricmp (Argv[0], "-n") == 0) { Options->NoLast = 1; } else if (((stricmp (Argv[0], "-v") == 0)) || ((stricmp (Argv[0], "--verbose") == 0))) { // // -v for verbose // Options->Verbose = 1; } else if (stricmp (Argv[0], "--debug") == 0) { Status = AsciiStringToUint64(Argv[1], FALSE, &DebugLevel); if (EFI_ERROR (Status)) { Error (NULL, 0, 2000, "Invalid option value", "%s = %s", Argv[0], Argv[1]); ReturnStatus = 1; goto Done; } if (DebugLevel > 9) { Error (NULL, 0, 2000, "Invalid option value", "Debug Level range is 0-9, current input level is %llu", DebugLevel); ReturnStatus = 1; goto Done; } if (DebugLevel>=5 && DebugLevel<=9) { Options->Debug = TRUE; } else { Options->Debug = FALSE; } Argv++; Argc--; } else if ((stricmp (Argv[0], "--quiet") == 0) || (stricmp (Argv[0], "-q") == 0)) { Options->Quiet = TRUE; } else if ((stricmp (Argv[0], "--dump") == 0) || (stricmp (Argv[0], "-d") == 0)) { // // -dump for dumping a ROM image. In this case, say that the device id // and vendor id are valid so we don't have to specify bogus ones on the // command line. // Options->DumpOption = 1; Options->VendIdValid = 1; Options->DevIdCount = 1; FileFlags = FILE_FLAG_BINARY; } else if ((stricmp (Argv[0], "-l") == 0) || (stricmp (Argv[0], "--class-code") == 0)) { // // Class code value for the next file in the list. // Make sure there's another parameter // Status = AsciiStringToUint64(Argv[1], FALSE, &TempValue); if (EFI_ERROR (Status)) { Error (NULL, 0, 2000, "Invalid option value", "%s = %s", Argv[0], Argv[1]); ReturnStatus = 1; goto Done; } ClassCode = (UINT32) TempValue; if (ClassCode & 0xFF000000) { Error (NULL, 0, 2000, "Invalid parameter", "Class code %s out of range!", Argv[1]); ReturnStatus = STATUS_ERROR; goto Done; } if (FileList != NULL && FileList->ClassCode == 0) { FileList->ClassCode = ClassCode; } Argv++; Argc--; } else if ((stricmp (Argv[0], "-r") == 0) || (stricmp (Argv[0], "--Revision") == 0)) { // // Code revision in the PCI data structure. The value is for the next // file in the list. // Make sure there's another parameter // Status = AsciiStringToUint64(Argv[1], FALSE, &TempValue); if (EFI_ERROR (Status)) { Error (NULL, 0, 2000, "Invalid option value", "%s = %s", Argv[0], Argv[1]); ReturnStatus = 1; goto Done; } CodeRevision = (UINT32) TempValue; if (CodeRevision & 0xFFFF0000) { Error (NULL, 0, 2000, "Invalid parameter", "Code revision %s out of range!", Argv[1]); ReturnStatus = STATUS_ERROR; goto Done; } if (FileList != NULL && FileList->CodeRevision == 0) { FileList->CodeRevision = (UINT16) CodeRevision; } Argv++; Argc--; } else if ((stricmp (Argv[0], "-p") == 0) || (stricmp (Argv[0], "--pci23") == 0)) { // // Default layout meets PCI 3.0 specifications, specifying this flag will for a PCI 2.3 layout. // mOptions.Pci23 = 1; } else { Error (NULL, 0, 2000, "Invalid parameter", "Invalid option specified: %s", Argv[0]); ReturnStatus = STATUS_ERROR; goto Done; } } else { // // Not a slash-option argument. Must be a file name. Make sure they've specified // -e or -b already. // if ((FileFlags & (FILE_FLAG_BINARY | FILE_FLAG_EFI)) == 0) { Error (NULL, 0, 2000, "Invalid parameter", "Missing -e or -b with input file %s!", Argv[0]); ReturnStatus = STATUS_ERROR; goto Done; } // // Check Efi Option RomImage // if ((FileFlags & FILE_FLAG_EFI) == FILE_FLAG_EFI) { EfiRomFlag = TRUE; } // // Create a new file structure // FileList = (FILE_LIST *) malloc (sizeof (FILE_LIST)); if (FileList == NULL) { Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!", NULL); ReturnStatus = STATUS_ERROR; goto Done; } // // set flag and class code for this image. // memset ((char *) FileList, 0, sizeof (FILE_LIST)); FileList->FileName = Argv[0]; FileList->FileFlags = FileFlags; FileList->ClassCode = ClassCode; FileList->CodeRevision = (UINT16) CodeRevision; ClassCode = 0; CodeRevision = 0; if (Options->FileList == NULL) { Options->FileList = FileList; } else { if (PrevFileList == NULL) { PrevFileList = FileList; } else { PrevFileList->Next = FileList; } } PrevFileList = FileList; } // // Next argument // Argv++; Argc--; } // // Must have specified some files // if (Options->FileList == NULL) { Error (NULL, 0, 2000, "Invalid parameter", "Missing input file name!"); // // No memory allocation, return directly. // return STATUS_ERROR; } // // For EFI OptionRom image, Make sure a device ID and vendor ID are both specified. // if (EfiRomFlag) { if (!Options->VendIdValid) { Error (NULL, 0, 2000, "Missing Vendor ID in command line", NULL); ReturnStatus = STATUS_ERROR; goto Done; } if (!Options->DevIdCount) { Error (NULL, 0, 2000, "Missing Device ID in command line", NULL); ReturnStatus = STATUS_ERROR; goto Done; } } if (Options->DevIdCount > 1 && Options->Pci23) { Error (NULL, 0, 2000, "Invalid parameter", "PCI 3.0 is required when specifying multiple Device IDs"); ReturnStatus = STATUS_ERROR; goto Done; } Done: if (ReturnStatus != 0) { while (Options->FileList != NULL) { FileList = Options->FileList->Next; free (Options->FileList); Options->FileList = FileList; } } return ReturnStatus; } static void Version ( VOID ) /*++ Routine Description: Print version information for this utility. Arguments: None. Returns: Nothing. --*/ { fprintf (stdout, "%s Version %d.%d %s \n", UTILITY_NAME, UTILITY_MAJOR_VERSION, UTILITY_MINOR_VERSION, __BUILD_VERSION); } static void Usage ( VOID ) /*++ Routine Description: Print usage information for this utility. Arguments: None. Returns: Nothing. --*/ { // // Summary usage // fprintf (stdout, "Usage: %s -f VendorId -i DeviceId [options] [file name] \n\n", UTILITY_NAME); // // Copyright declaration // fprintf (stdout, "Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.\n\n"); // // Details Option // fprintf (stdout, "Options:\n"); fprintf (stdout, " -o FileName, --output FileName\n\ File will be created to store the output content.\n"); fprintf (stdout, " -e EfiFileName\n\ EFI PE32 image files.\n"); fprintf (stdout, " -ec EfiFileName\n\ EFI PE32 image files and will be compressed.\n"); fprintf (stdout, " -b BinFileName\n\ Legacy binary files.\n"); fprintf (stdout, " -l ClassCode\n\ Hex ClassCode in the PCI data structure header.\n"); fprintf (stdout, " -r Rev Hex Revision in the PCI data structure header.\n"); fprintf (stdout, " -n Not to automatically set the LAST bit in the last file.\n"); fprintf (stdout, " -f VendorId\n\ Hex PCI Vendor ID for the device OpROM, must be specified\n"); fprintf (stdout, " -i DeviceId\n\ One or more hex PCI Device IDs for the device OpROM, must be specified\n"); fprintf (stdout, " -p, --pci23\n\ Default layout meets PCI 3.0 specifications\n\ specifying this flag will for a PCI 2.3 layout.\n"); fprintf (stdout, " -d, --dump\n\ Dump the headers of an existing option ROM image.\n"); fprintf (stdout, " -v, --verbose\n\ Turn on verbose output with informational messages.\n"); fprintf (stdout, " --version Show program's version number and exit.\n"); fprintf (stdout, " -h, --help\n\ Show this help message and exit.\n"); fprintf (stdout, " -q, --quiet\n\ Disable all messages except FATAL ERRORS.\n"); fprintf (stdout, " --debug [#,0-9]\n\ Enable debug messages at level #.\n"); } static void DumpImage ( FILE_LIST *InFile ) /*++ Routine Description: Dump the headers of an existing option ROM image Arguments: InFile - the file name of an existing option ROM image Returns: none --*/ { PCI_EXPANSION_ROM_HEADER PciRomHdr; FILE *InFptr; UINT32 ImageStart; UINT32 ImageCount; EFI_PCI_EXPANSION_ROM_HEADER EfiRomHdr; PCI_DATA_STRUCTURE PciDs23; PCI_3_0_DATA_STRUCTURE PciDs30; UINT16 DevId; // // Open the input file // if ((InFptr = fopen (LongFilePath (InFile->FileName), "rb")) == NULL) { Error (NULL, 0, 0001, "Error opening file", InFile->FileName); return ; } // // Go through the image and dump the header stuff for each // ImageCount = 0; for (;;) { // // Save our position in the file, since offsets in the headers // are relative to the particular image. // ImageStart = ftell (InFptr); ImageCount++; // // Read the option ROM header. Have to assume a raw binary image for now. // if (fread (&PciRomHdr, sizeof (PciRomHdr), 1, InFptr) != 1) { Error (NULL, 0, 3001, "Not supported", "Failed to read PCI ROM header from file!"); goto BailOut; } // // Dump the contents of the header // fprintf (stdout, "Image %u -- Offset 0x%X\n", (unsigned) ImageCount, (unsigned) ImageStart); fprintf (stdout, " ROM header contents\n"); fprintf (stdout, " Signature 0x%04X\n", PciRomHdr.Signature); fprintf (stdout, " PCIR offset 0x%04X\n", PciRomHdr.PcirOffset); // // Find PCI data structure // if (fseek (InFptr, ImageStart + PciRomHdr.PcirOffset, SEEK_SET)) { Error (NULL, 0, 3001, "Not supported", "Failed to seek to PCI data structure!"); goto BailOut; } // // Read and dump the PCI data structure // memset (&PciDs23, 0, sizeof (PciDs23)); memset (&PciDs30, 0, sizeof (PciDs30)); if (mOptions.Pci23 == 1) { if (fread (&PciDs23, sizeof (PciDs23), 1, InFptr) != 1) { Error (NULL, 0, 3001, "Not supported", "Failed to read PCI data structure from file %s!", InFile->FileName); goto BailOut; } } else { if (fread (&PciDs30, sizeof (PciDs30), 1, InFptr) != 1) { Error (NULL, 0, 3001, "Not supported", "Failed to read PCI data structure from file %s!", InFile->FileName); goto BailOut; } } if (mOptions.Verbose) { VerboseMsg("Read PCI data structure from file %s", InFile->FileName); } //fprintf (stdout, " PCI Data Structure\n"); if (mOptions.Pci23 == 1) { fprintf ( stdout, " Signature %c%c%c%c\n", (char) PciDs23.Signature, (char) (PciDs23.Signature >> 8), (char) (PciDs23.Signature >> 16), (char) (PciDs23.Signature >> 24) ); fprintf (stdout, " Vendor ID 0x%04X\n", PciDs23.VendorId); fprintf (stdout, " Device ID 0x%04X\n", PciDs23.DeviceId); fprintf (stdout, " Length 0x%04X\n", PciDs23.Length); fprintf (stdout, " Revision 0x%04X\n", PciDs23.Revision); fprintf ( stdout, " Class Code 0x%06X\n", (unsigned) (PciDs23.ClassCode[0] | (PciDs23.ClassCode[1] << 8) | (PciDs23.ClassCode[2] << 16)) ); fprintf (stdout, " Image size 0x%X\n", (unsigned) PciDs23.ImageLength * 512); fprintf (stdout, " Code revision: 0x%04X\n", PciDs23.CodeRevision); fprintf (stdout, " Indicator 0x%02X", PciDs23.Indicator); } else { fprintf ( stdout, " Signature %c%c%c%c\n", (char) PciDs30.Signature, (char) (PciDs30.Signature >> 8), (char) (PciDs30.Signature >> 16), (char) (PciDs30.Signature >> 24) ); fprintf (stdout, " Vendor ID 0x%04X\n", PciDs30.VendorId); fprintf (stdout, " Device ID 0x%04X\n", PciDs30.DeviceId); fprintf (stdout, " Length 0x%04X\n", PciDs30.Length); fprintf (stdout, " Revision 0x%04X\n", PciDs30.Revision); fprintf (stdout, " DeviceListOffset 0x%02X\n", PciDs30.DeviceListOffset); if (PciDs30.DeviceListOffset) { // // Print device ID list // fprintf (stdout, " Device list contents\n"); if (fseek (InFptr, ImageStart + PciRomHdr.PcirOffset + PciDs30.DeviceListOffset, SEEK_SET)) { Error (NULL, 0, 3001, "Not supported", "Failed to seek to PCI device ID list!"); goto BailOut; } // // Loop until terminating 0 // do { if (fread (&DevId, sizeof (DevId), 1, InFptr) != 1) { Error (NULL, 0, 3001, "Not supported", "Failed to read PCI device ID list from file %s!", InFile->FileName); goto BailOut; } if (DevId) { fprintf (stdout, " 0x%04X\n", DevId); } } while (DevId); } fprintf ( stdout, " Class Code 0x%06X\n", (unsigned) (PciDs30.ClassCode[0] | (PciDs30.ClassCode[1] << 8) | (PciDs30.ClassCode[2] << 16)) ); fprintf (stdout, " Image size 0x%X\n", (unsigned) PciDs30.ImageLength * 512); fprintf (stdout, " Code revision: 0x%04X\n", PciDs30.CodeRevision); fprintf (stdout, " MaxRuntimeImageLength 0x%02X\n", PciDs30.MaxRuntimeImageLength); fprintf (stdout, " ConfigUtilityCodeHeaderOffset 0x%02X\n", PciDs30.ConfigUtilityCodeHeaderOffset); fprintf (stdout, " DMTFCLPEntryPointOffset 0x%02X\n", PciDs30.DMTFCLPEntryPointOffset); fprintf (stdout, " Indicator 0x%02X", PciDs30.Indicator); } // // Print the indicator, used to flag the last image // if (PciDs23.Indicator == INDICATOR_LAST || PciDs30.Indicator == INDICATOR_LAST) { fprintf (stdout, " (last image)\n"); } else { fprintf (stdout, "\n"); } // // Print the code type. If EFI code, then we can provide more info. // if (mOptions.Pci23 == 1) { fprintf (stdout, " Code type 0x%02X", PciDs23.CodeType); } else { fprintf (stdout, " Code type 0x%02X", PciDs30.CodeType); } if (PciDs23.CodeType == PCI_CODE_TYPE_EFI_IMAGE || PciDs30.CodeType == PCI_CODE_TYPE_EFI_IMAGE) { fprintf (stdout, " (EFI image)\n"); // // Re-read the header as an EFI ROM header, then dump more info // fprintf (stdout, " EFI ROM header contents\n"); if (fseek (InFptr, ImageStart, SEEK_SET)) { Error (NULL, 0, 5001, "Failed to re-seek to ROM header structure!", NULL); goto BailOut; } if (fread (&EfiRomHdr, sizeof (EfiRomHdr), 1, InFptr) != 1) { Error (NULL, 0, 5001, "Failed to read EFI PCI ROM header from file!", NULL); goto BailOut; } // // Now dump more info // fprintf (stdout, " EFI Signature 0x%04X\n", (unsigned) EfiRomHdr.EfiSignature); fprintf ( stdout, " Compression Type 0x%04X ", EfiRomHdr.CompressionType ); if (EfiRomHdr.CompressionType == EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) { fprintf (stdout, "(compressed)\n"); } else { fprintf (stdout, "(not compressed)\n"); } fprintf ( stdout, " Machine type 0x%04X (%s)\n", EfiRomHdr.EfiMachineType, GetMachineTypeStr (EfiRomHdr.EfiMachineType) ); fprintf ( stdout, " Subsystem 0x%04X (%s)\n", EfiRomHdr.EfiSubsystem, GetSubsystemTypeStr (EfiRomHdr.EfiSubsystem) ); fprintf ( stdout, " EFI image offset 0x%04X (@0x%X)\n", EfiRomHdr.EfiImageHeaderOffset, EfiRomHdr.EfiImageHeaderOffset + (unsigned) ImageStart ); } else { // // Not an EFI image // fprintf (stdout, "\n"); } // // If code type is EFI image, then dump it as well? // // if (PciDs.CodeType == PCI_CODE_TYPE_EFI_IMAGE) { // } // // If last image, then we're done // if (PciDs23.Indicator == INDICATOR_LAST || PciDs30.Indicator == INDICATOR_LAST) { goto BailOut; } // // Seek to the start of the next image // if (mOptions.Pci23 == 1) { if (fseek (InFptr, ImageStart + (PciDs23.ImageLength * 512), SEEK_SET)) { Error (NULL, 0, 3001, "Not supported", "Failed to seek to next image!"); goto BailOut; } } else { if (fseek (InFptr, ImageStart + (PciDs30.ImageLength * 512), SEEK_SET)) { Error (NULL, 0, 3001, "Not supported", "Failed to seek to next image!"); goto BailOut; } } } BailOut: fclose (InFptr); } char * GetMachineTypeStr ( UINT16 MachineType ) /*++ Routine Description: GC_TODO: Add function description Arguments: MachineType - GC_TODO: add argument description Returns: GC_TODO: add return values --*/ { int Index; for (Index = 0; mMachineTypes[Index].Name != NULL; Index++) { if (mMachineTypes[Index].Value == MachineType) { return mMachineTypes[Index].Name; } } return "unknown"; } static char * GetSubsystemTypeStr ( UINT16 SubsystemType ) /*++ Routine Description: GC_TODO: Add function description Arguments: SubsystemType - GC_TODO: add argument description Returns: GC_TODO: add return values --*/ { int Index; for (Index = 0; mSubsystemTypes[Index].Name != NULL; Index++) { if (mSubsystemTypes[Index].Value == SubsystemType) { return mSubsystemTypes[Index].Name; } } return "unknown"; }