这里介绍一下解压缩 EFI_DECOMPRESS_PROTOCOL 的使用。
首先是 GetInfo 函数【参考1】。通过它我们能够获得压缩文件的一些基本信息,比如:解压后的大小,解压过程需要的临时内存空间的大小。
之后就是具体的解压函数 Decompress 【参考2】
根据上面的信息,编写一个简单的测试程序,首先将压缩格式的文件读取到内存中,再使用 GetInfo 取得必要的信息,最后,根据必须要的信息创建内存 Buffer ,使用 Decompress 解压即可。
代码如下
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Protocol/Decompress.h> #include <Library/BaseMemoryLib.h> #include <Library/MemoryAllocationLib.h> #include <Protocol/EfiShell.h> #include <Library/ShellLib.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; int EFIAPI main ( IN int Argc, IN CHAR16 **Argv ) { EFI_DECOMPRESS_PROTOCOL *Decompress; VOID *ImageBuffer=NULL; UINT32 ImageLength=0; UINT32 DestinationSize; UINT8 *Scratch; UINT32 ScratchSize; VOID *DecompressedImageBuffer=NULL; EFI_STATUS Status; EFI_FILE_HANDLE FileHandle; EFI_FILE_INFO *FileInfo = NULL; UINTN ReadSize; EFI_HANDLE *HandleBuffer=NULL; Status = gBS->LocateProtocol (&gEfiDecompressProtocolGuid, NULL, (VOID**)&Decompress); if (EFI_ERROR (Status)) { Print(L"Can't find Decompress Protocol! \n"); } else { //Open the file given by the parameter Status = ShellOpenFileByName( Argv[1], (SHELL_FILE_HANDLE *)&FileHandle, EFI_FILE_MODE_READ, 0); if(Status != RETURN_SUCCESS) { Print(L"OpenFile failed!\n"); return EFI_SUCCESS; }//if(Status != RETURN_SUCCESS) { //Get file size FileInfo = ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle); //Allocate a memory buffer HandleBuffer = AllocateZeroPool((UINTN) FileInfo->FileSize); if (HandleBuffer == NULL) { return (SHELL_OUT_OF_RESOURCES); } ReadSize=(UINTN) FileInfo-> FileSize; //Load the whole file to the buffer Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer); //Close the source file ShellCloseFile(&FileHandle); Status = Decompress->GetInfo ( Decompress, HandleBuffer, ReadSize, &DestinationSize, &ScratchSize ); if (!EFI_ERROR (Status)) { Print(L"[GetInfo] Destination Size %d\n",DestinationSize); Print(L"[GetInfo] Scratch Size %d\n",ScratchSize); DecompressedImageBuffer = AllocateZeroPool (DestinationSize); if (DecompressedImageBuffer != NULL) { Scratch = AllocateZeroPool (ScratchSize); if (Scratch != NULL) { Status = Decompress->Decompress( Decompress, HandleBuffer, //Source ReadSize, //Source Size DecompressedImageBuffer,//Destination DestinationSize, //DestinationSize Scratch, ScratchSize ); if (!EFI_ERROR (Status)) { ImageBuffer = DecompressedImageBuffer; ImageLength = DestinationSize; //Create a new file Status = ShellOpenFileByName(L"decomp.bmp", (SHELL_FILE_HANDLE *)&FileHandle, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE| EFI_FILE_MODE_CREATE, 0); if(Status != RETURN_SUCCESS) { Print(L"CreatFile failed [%r]!\n",Status); return EFI_SUCCESS; } Status = ShellWriteFile(FileHandle, &DestinationSize, ImageBuffer ); //Close the source file ShellCloseFile(&FileHandle); } //if (!EFI_ERROR (Status)) { else { Print(L"Decompress error [%r] \n",Status); } FreePool (Scratch); } // if (Scratch != NULL) { } //if (ImageBuffer != NULL) { } //if (!EFI_ERROR (Status)) { else { Print(L"Read compressed file error!\n",ScratchSize); } } //if (EFI_ERROR (Status)) { return EFI_SUCCESS; }
运行结果
完整程序下载
特别注意:
1.本文测试使用的压缩文件是 UEFI 下面生成的,具体命令是
eficompress testc.bmp compressed.z
我尝试使用 BaseTools 里面的压缩工具,生成的文件格式会出现不兼容,无法正常解压的情况。
2.根据我自己的理解 Scratch Buffer 是解压过程中解压算法使用的内存区域,不同压缩文件对于这片区域的大小要求不同。
参考:
1. UEFI Spec 2.4 P883
2. UEFI Spec 2.4 P885
3. ShellPkg 中的 EfiDecompress 程序是非常好的参考例子。