]> git.proxmox.com Git - mirror_edk2.git/blobdiff - BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
BaseTools: Add FMMT Python Tool
[mirror_edk2.git] / BaseTools / Source / Python / FMMT / core / BinaryFactoryProduct.py
diff --git a/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
new file mode 100644 (file)
index 0000000..2d4e6d9
--- /dev/null
@@ -0,0 +1,380 @@
+## @file\r
+# This file is used to implement of the various bianry parser.\r
+#\r
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>\r
+# SPDX-License-Identifier: BSD-2-Clause-Patent\r
+##\r
+from re import T\r
+import copy\r
+import os\r
+import sys\r
+from FirmwareStorageFormat.Common import *\r
+from core.BiosTreeNode import *\r
+from core.BiosTree import *\r
+from core.GuidTools import GUIDTools\r
+from utils.FmmtLogger import FmmtLogger as logger\r
+\r
+ROOT_TREE = 'ROOT'\r
+ROOT_FV_TREE = 'ROOT_FV_TREE'\r
+ROOT_FFS_TREE = 'ROOT_FFS_TREE'\r
+ROOT_SECTION_TREE = 'ROOT_SECTION_TREE'\r
+\r
+FV_TREE = 'FV'\r
+DATA_FV_TREE = 'DATA_FV'\r
+FFS_TREE = 'FFS'\r
+FFS_PAD = 'FFS_PAD'\r
+FFS_FREE_SPACE = 'FFS_FREE_SPACE'\r
+SECTION_TREE = 'SECTION'\r
+SEC_FV_TREE = 'SEC_FV_IMAGE'\r
+BINARY_DATA = 'BINARY'\r
+Fv_count = 0\r
+\r
+## Abstract factory\r
+class BinaryFactory():\r
+    type:list = []\r
+\r
+    def Create_Product():\r
+        pass\r
+\r
+class BinaryProduct():\r
+    ## Use GuidTool to decompress data.\r
+    def DeCompressData(self, GuidTool, Section_Data: bytes, FileName) -> bytes:\r
+        guidtool = GUIDTools().__getitem__(struct2stream(GuidTool))\r
+        if not guidtool.ifexist:\r
+            logger.error("GuidTool {} is not found when decompressing {} file.\n".format(guidtool.command, FileName))\r
+            raise Exception("Process Failed: GuidTool not found!")\r
+        DecompressedData = guidtool.unpack(Section_Data)\r
+        return DecompressedData\r
+\r
+    def ParserData():\r
+        pass\r
+\r
+class SectionFactory(BinaryFactory):\r
+    type = [SECTION_TREE]\r
+\r
+    def Create_Product():\r
+        return SectionProduct()\r
+\r
+class FfsFactory(BinaryFactory):\r
+    type = [ROOT_SECTION_TREE, FFS_TREE]\r
+\r
+    def Create_Product():\r
+        return FfsProduct()\r
+\r
+class FvFactory(BinaryFactory):\r
+    type = [ROOT_FFS_TREE, FV_TREE, SEC_FV_TREE]\r
+\r
+    def Create_Product():\r
+        return FvProduct()\r
+\r
+class FdFactory(BinaryFactory):\r
+    type = [ROOT_FV_TREE, ROOT_TREE]\r
+\r
+    def Create_Product():\r
+        return FdProduct()\r
+\r
+class SectionProduct(BinaryProduct):\r
+    ## Decompress the compressed section.\r
+    def ParserData(self, Section_Tree, whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:\r
+        if Section_Tree.Data.Type == 0x01:\r
+            Section_Tree.Data.OriData = Section_Tree.Data.Data\r
+            self.ParserSection(Section_Tree, b'')\r
+        # Guided Define Section\r
+        elif Section_Tree.Data.Type == 0x02:\r
+            Section_Tree.Data.OriData = Section_Tree.Data.Data\r
+            DeCompressGuidTool = Section_Tree.Data.ExtHeader.SectionDefinitionGuid\r
+            Section_Tree.Data.Data = self.DeCompressData(DeCompressGuidTool, Section_Tree.Data.Data, Section_Tree.Parent.Data.Name)\r
+            Section_Tree.Data.Size = len(Section_Tree.Data.Data) + Section_Tree.Data.HeaderLength\r
+            self.ParserSection(Section_Tree, b'')\r
+        elif Section_Tree.Data.Type == 0x03:\r
+            Section_Tree.Data.OriData = Section_Tree.Data.Data\r
+            self.ParserSection(Section_Tree, b'')\r
+        # SEC_FV Section\r
+        elif Section_Tree.Data.Type == 0x17:\r
+            global Fv_count\r
+            Sec_Fv_Info = FvNode(Fv_count, Section_Tree.Data.Data)\r
+            Sec_Fv_Tree = BIOSTREE('FV'+ str(Fv_count))\r
+            Sec_Fv_Tree.type = SEC_FV_TREE\r
+            Sec_Fv_Tree.Data = Sec_Fv_Info\r
+            Sec_Fv_Tree.Data.HOffset = Section_Tree.Data.DOffset\r
+            Sec_Fv_Tree.Data.DOffset = Sec_Fv_Tree.Data.HOffset + Sec_Fv_Tree.Data.Header.HeaderLength\r
+            Sec_Fv_Tree.Data.Data = Section_Tree.Data.Data[Sec_Fv_Tree.Data.Header.HeaderLength:]\r
+            Section_Tree.insertChild(Sec_Fv_Tree)\r
+            Fv_count += 1\r
+\r
+    def ParserSection(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:\r
+        Rel_Offset = 0\r
+        Section_Offset = 0\r
+        # Get the Data from parent tree, if do not have the tree then get it from the whole_data.\r
+        if ParTree.Data != None:\r
+            Data_Size = len(ParTree.Data.Data)\r
+            Section_Offset = ParTree.Data.DOffset\r
+            Whole_Data = ParTree.Data.Data\r
+        else:\r
+            Data_Size = len(Whole_Data)\r
+        # Parser all the data to collect all the Section recorded in its Parent Section.\r
+        while Rel_Offset < Data_Size:\r
+            # Create a SectionNode and set it as the SectionTree's Data\r
+            Section_Info = SectionNode(Whole_Data[Rel_Offset:])\r
+            Section_Tree = BIOSTREE(Section_Info.Name)\r
+            Section_Tree.type = SECTION_TREE\r
+            Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]\r
+            Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset\r
+            Section_Info.HOffset = Section_Offset + Rel_Whole_Offset\r
+            Section_Info.ROffset = Rel_Offset\r
+            if Section_Info.Header.Type == 0:\r
+                break\r
+            # The final Section in parent Section does not need to add padding, else must be 4-bytes align with parent Section start offset\r
+            Pad_Size = 0\r
+            if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):\r
+                Pad_Size = GetPadSize(Section_Info.Size, SECTION_COMMON_ALIGNMENT)\r
+                Section_Info.PadData = Pad_Size * b'\x00'\r
+            if Section_Info.Header.Type == 0x02:\r
+                Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset\r
+                Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset: Rel_Offset+Section_Info.Size]\r
+            if Section_Info.Header.Type == 0x14:\r
+                ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()\r
+            if Section_Info.Header.Type == 0x15:\r
+                ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()\r
+            if Section_Info.Header.Type == 0x19:\r
+                if Section_Info.Data.replace(b'\x00', b'') == b'':\r
+                    Section_Info.IsPadSection = True\r
+            Section_Offset += Section_Info.Size + Pad_Size\r
+            Rel_Offset += Section_Info.Size + Pad_Size\r
+            Section_Tree.Data = Section_Info\r
+            ParTree.insertChild(Section_Tree)\r
+\r
+class FfsProduct(BinaryProduct):\r
+    # ParserFFs / GetSection\r
+    def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:\r
+        Rel_Offset = 0\r
+        Section_Offset = 0\r
+        # Get the Data from parent tree, if do not have the tree then get it from the whole_data.\r
+        if ParTree.Data != None:\r
+            Data_Size = len(ParTree.Data.Data)\r
+            Section_Offset = ParTree.Data.DOffset\r
+            Whole_Data = ParTree.Data.Data\r
+        else:\r
+            Data_Size = len(Whole_Data)\r
+        # Parser all the data to collect all the Section recorded in Ffs.\r
+        while Rel_Offset < Data_Size:\r
+            # Create a SectionNode and set it as the SectionTree's Data\r
+            Section_Info = SectionNode(Whole_Data[Rel_Offset:])\r
+            Section_Tree = BIOSTREE(Section_Info.Name)\r
+            Section_Tree.type = SECTION_TREE\r
+            Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]\r
+            Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset\r
+            Section_Info.HOffset = Section_Offset + Rel_Whole_Offset\r
+            Section_Info.ROffset = Rel_Offset\r
+            if Section_Info.Header.Type == 0:\r
+                break\r
+            # The final Section in Ffs does not need to add padding, else must be 4-bytes align with Ffs start offset\r
+            Pad_Size = 0\r
+            if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):\r
+                Pad_Size = GetPadSize(Section_Info.Size, SECTION_COMMON_ALIGNMENT)\r
+                Section_Info.PadData = Pad_Size * b'\x00'\r
+            if Section_Info.Header.Type == 0x02:\r
+                Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset\r
+                Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset: Rel_Offset+Section_Info.Size]\r
+            # If Section is Version or UI type, it saves the version and UI info of its parent Ffs.\r
+            if Section_Info.Header.Type == 0x14:\r
+                ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()\r
+            if Section_Info.Header.Type == 0x15:\r
+                ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()\r
+            if Section_Info.Header.Type == 0x19:\r
+                if Section_Info.Data.replace(b'\x00', b'') == b'':\r
+                    Section_Info.IsPadSection = True\r
+            Section_Offset += Section_Info.Size + Pad_Size\r
+            Rel_Offset += Section_Info.Size + Pad_Size\r
+            Section_Tree.Data = Section_Info\r
+            ParTree.insertChild(Section_Tree)\r
+\r
+class FvProduct(BinaryProduct):\r
+    ##  ParserFv / GetFfs\r
+    def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:\r
+        Ffs_Offset = 0\r
+        Rel_Offset = 0\r
+        # Get the Data from parent tree, if do not have the tree then get it from the whole_data.\r
+        if ParTree.Data != None:\r
+            Data_Size = len(ParTree.Data.Data)\r
+            Ffs_Offset = ParTree.Data.DOffset\r
+            Whole_Data = ParTree.Data.Data\r
+        else:\r
+            Data_Size = len(Whole_Data)\r
+        # Parser all the data to collect all the Ffs recorded in Fv.\r
+        while Rel_Offset < Data_Size:\r
+            # Create a FfsNode and set it as the FFsTree's Data\r
+            if Data_Size - Rel_Offset < 24:\r
+                Ffs_Tree = BIOSTREE('Free_Space')\r
+                Ffs_Tree.type = FFS_FREE_SPACE\r
+                Ffs_Tree.Data = FreeSpaceNode(Whole_Data[Rel_Offset:])\r
+                Ffs_Tree.Data.HOffset = Ffs_Offset + Rel_Whole_Offset\r
+                Ffs_Tree.Data.DOffset = Ffs_Tree.Data.HOffset\r
+                ParTree.Data.Free_Space = Data_Size - Rel_Offset\r
+                ParTree.insertChild(Ffs_Tree)\r
+                Rel_Offset = Data_Size\r
+            else:\r
+                Ffs_Info = FfsNode(Whole_Data[Rel_Offset:])\r
+                Ffs_Tree = BIOSTREE(Ffs_Info.Name)\r
+                Ffs_Info.HOffset = Ffs_Offset + Rel_Whole_Offset\r
+                Ffs_Info.DOffset = Ffs_Offset + Ffs_Info.Header.HeaderLength + Rel_Whole_Offset\r
+                Ffs_Info.ROffset = Rel_Offset\r
+                if Ffs_Info.Name == PADVECTOR:\r
+                    Ffs_Tree.type = FFS_PAD\r
+                    Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]\r
+                    Ffs_Info.Size = len(Ffs_Info.Data) + Ffs_Info.Header.HeaderLength\r
+                    # if current Ffs is the final ffs of Fv and full of b'\xff', define it with Free_Space\r
+                    if struct2stream(Ffs_Info.Header).replace(b'\xff', b'') == b'':\r
+                        Ffs_Tree.type = FFS_FREE_SPACE\r
+                        Ffs_Info.Data = Whole_Data[Rel_Offset:]\r
+                        Ffs_Info.Size = len(Ffs_Info.Data)\r
+                        ParTree.Data.Free_Space = Ffs_Info.Size\r
+                else:\r
+                    Ffs_Tree.type = FFS_TREE\r
+                    Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]\r
+                # The final Ffs in Fv does not need to add padding, else must be 8-bytes align with Fv start offset\r
+                Pad_Size = 0\r
+                if Ffs_Tree.type != FFS_FREE_SPACE and (Rel_Offset+Ffs_Info.Header.HeaderLength+len(Ffs_Info.Data) != Data_Size):\r
+                    Pad_Size = GetPadSize(Ffs_Info.Size, FFS_COMMON_ALIGNMENT)\r
+                    Ffs_Info.PadData = Pad_Size * b'\xff'\r
+                Ffs_Offset += Ffs_Info.Size + Pad_Size\r
+                Rel_Offset += Ffs_Info.Size + Pad_Size\r
+                Ffs_Tree.Data = Ffs_Info\r
+                ParTree.insertChild(Ffs_Tree)\r
+\r
+class FdProduct(BinaryProduct):\r
+    type = [ROOT_FV_TREE, ROOT_TREE]\r
+\r
+    ## Create DataTree with first level /fv Info, then parser each Fv.\r
+    def ParserData(self, WholeFvTree, whole_data: bytes=b'', offset: int=0) -> None:\r
+        # Get all Fv image in Fd with offset and length\r
+        Fd_Struct = self.GetFvFromFd(whole_data)\r
+        data_size = len(whole_data)\r
+        Binary_count = 0\r
+        global Fv_count\r
+        # If the first Fv image is the Binary Fv, add it into the tree.\r
+        if Fd_Struct[0][1] != 0:\r
+            Binary_node = BIOSTREE('BINARY'+ str(Binary_count))\r
+            Binary_node.type = BINARY_DATA\r
+            Binary_node.Data = BinaryNode(str(Binary_count))\r
+            Binary_node.Data.Data = whole_data[:Fd_Struct[0][1]]\r
+            Binary_node.Data.Size = len(Binary_node.Data.Data)\r
+            Binary_node.Data.HOffset = 0 + offset\r
+            WholeFvTree.insertChild(Binary_node)\r
+            Binary_count += 1\r
+        # Add the first collected Fv image into the tree.\r
+        Cur_node = BIOSTREE(Fd_Struct[0][0]+ str(Fv_count))\r
+        Cur_node.type = Fd_Struct[0][0]\r
+        Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[0][1]:Fd_Struct[0][1]+Fd_Struct[0][2][0]])\r
+        Cur_node.Data.HOffset = Fd_Struct[0][1] + offset\r
+        Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength\r
+        Cur_node.Data.Data = whole_data[Fd_Struct[0][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[0][1]+Cur_node.Data.Size]\r
+        WholeFvTree.insertChild(Cur_node)\r
+        Fv_count += 1\r
+        Fv_num = len(Fd_Struct)\r
+        # Add all the collected Fv image and the Binary Fv image between them into the tree.\r
+        for i in range(Fv_num-1):\r
+            if Fd_Struct[i][1]+Fd_Struct[i][2][0] != Fd_Struct[i+1][1]:\r
+                Binary_node = BIOSTREE('BINARY'+ str(Binary_count))\r
+                Binary_node.type = BINARY_DATA\r
+                Binary_node.Data = BinaryNode(str(Binary_count))\r
+                Binary_node.Data.Data = whole_data[Fd_Struct[i][1]+Fd_Struct[i][2][0]:Fd_Struct[i+1][1]]\r
+                Binary_node.Data.Size = len(Binary_node.Data.Data)\r
+                Binary_node.Data.HOffset = Fd_Struct[i][1]+Fd_Struct[i][2][0] + offset\r
+                WholeFvTree.insertChild(Binary_node)\r
+                Binary_count += 1\r
+            Cur_node = BIOSTREE(Fd_Struct[i+1][0]+ str(Fv_count))\r
+            Cur_node.type = Fd_Struct[i+1][0]\r
+            Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[i+1][1]:Fd_Struct[i+1][1]+Fd_Struct[i+1][2][0]])\r
+            Cur_node.Data.HOffset = Fd_Struct[i+1][1] + offset\r
+            Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength\r
+            Cur_node.Data.Data = whole_data[Fd_Struct[i+1][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[i+1][1]+Cur_node.Data.Size]\r
+            WholeFvTree.insertChild(Cur_node)\r
+            Fv_count += 1\r
+        # If the final Fv image is the Binary Fv, add it into the tree\r
+        if Fd_Struct[-1][1] + Fd_Struct[-1][2][0] != data_size:\r
+            Binary_node = BIOSTREE('BINARY'+ str(Binary_count))\r
+            Binary_node.type = BINARY_DATA\r
+            Binary_node.Data = BinaryNode(str(Binary_count))\r
+            Binary_node.Data.Data = whole_data[Fd_Struct[-1][1]+Fd_Struct[-1][2][0]:]\r
+            Binary_node.Data.Size = len(Binary_node.Data.Data)\r
+            Binary_node.Data.HOffset = Fd_Struct[-1][1]+Fd_Struct[-1][2][0] + offset\r
+            WholeFvTree.insertChild(Binary_node)\r
+            Binary_count += 1\r
+\r
+    ## Get the first level Fv from Fd file.\r
+    def GetFvFromFd(self, whole_data: bytes=b'') -> list:\r
+        Fd_Struct = []\r
+        data_size = len(whole_data)\r
+        cur_index = 0\r
+        # Get all the EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE FV image offset and length.\r
+        while cur_index < data_size:\r
+            if EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE in whole_data[cur_index:]:\r
+                target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE) + cur_index\r
+                if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE:\r
+                    Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])\r
+                    cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]\r
+                else:\r
+                    cur_index = target_index + 16\r
+            else:\r
+                cur_index = data_size\r
+        cur_index = 0\r
+        # Get all the EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE FV image offset and length.\r
+        while cur_index < data_size:\r
+            if EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE in whole_data[cur_index:]:\r
+                target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE) + cur_index\r
+                if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE:\r
+                    Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])\r
+                    cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]\r
+                else:\r
+                    cur_index = target_index + 16\r
+            else:\r
+                cur_index = data_size\r
+        cur_index = 0\r
+        # Get all the EFI_SYSTEM_NVDATA_FV_GUID_BYTE FV image offset and length.\r
+        while cur_index < data_size:\r
+            if EFI_SYSTEM_NVDATA_FV_GUID_BYTE in whole_data[cur_index:]:\r
+                target_index = whole_data[cur_index:].index(EFI_SYSTEM_NVDATA_FV_GUID_BYTE) + cur_index\r
+                if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE:\r
+                    Fd_Struct.append([DATA_FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])\r
+                    cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]\r
+                else:\r
+                    cur_index = target_index + 16\r
+            else:\r
+                cur_index = data_size\r
+        # Sort all the collect Fv image with offset.\r
+        Fd_Struct.sort(key=lambda x:x[1])\r
+        tmp_struct = copy.deepcopy(Fd_Struct)\r
+        tmp_index = 0\r
+        Fv_num = len(Fd_Struct)\r
+        # Remove the Fv image included in another Fv image.\r
+        for i in range(1,Fv_num):\r
+            if tmp_struct[i][1]+tmp_struct[i][2][0] < tmp_struct[i-1][1]+tmp_struct[i-1][2][0]:\r
+                Fd_Struct.remove(Fd_Struct[i-tmp_index])\r
+                tmp_index += 1\r
+        return Fd_Struct\r
+\r
+class ParserEntry():\r
+    FactoryTable:dict = {\r
+        SECTION_TREE: SectionFactory,\r
+        ROOT_SECTION_TREE: FfsFactory,\r
+        FFS_TREE: FfsFactory,\r
+        ROOT_FFS_TREE: FvFactory,\r
+        FV_TREE: FvFactory,\r
+        SEC_FV_TREE: FvFactory,\r
+        ROOT_FV_TREE: FdFactory,\r
+        ROOT_TREE: FdFactory,\r
+    }\r
+\r
+    def GetTargetFactory(self, Tree_type: str) -> BinaryFactory:\r
+        if Tree_type in self.FactoryTable:\r
+            return self.FactoryTable[Tree_type]\r
+\r
+    def Generate_Product(self, TargetFactory: BinaryFactory, Tree, Data: bytes, Offset: int) -> None:\r
+        New_Product = TargetFactory.Create_Product()\r
+        New_Product.ParserData(Tree, Data, Offset)\r
+\r
+    def DataParser(self, Tree, Data: bytes, Offset: int) -> None:\r
+        TargetFactory = self.GetTargetFactory(Tree.type)\r
+        if TargetFactory:\r
+            self.Generate_Product(TargetFactory, Tree, Data, Offset)\r
\ No newline at end of file