1## @file
2# Patch value into the binary file.
3#
4# Copyright (c) 2010 - 2014, Intel Corporation. All rights reserved.<BR>
5# This program and the accompanying materials
6# are licensed and made available under the terms and conditions of the BSD License
7# which accompanies this distribution.  The full text of the license may be found at
8# http://opensource.org/licenses/bsd-license.php
9#
10# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12#
13
14##
15# Import Modules
16#
17import Common.LongFilePathOs as os
18from Common.LongFilePathSupport import OpenLongFilePath as open
19import sys
20import re
21
22from optparse import OptionParser
23from optparse import make_option
24from Common.BuildToolError import *
25import Common.EdkLogger as EdkLogger
26from Common.BuildVersion import gBUILD_VERSION
27import array
28
29# Version and Copyright
30__version_number__ = ("0.10" + " " + gBUILD_VERSION)
31__version__ = "%prog Version " + __version_number__
32__copyright__ = "Copyright (c) 2010, Intel Corporation. All rights reserved."
33
34## PatchBinaryFile method
35#
36# This method mainly patches the data into binary file.
37#
38# @param FileName    File path of the binary file
39# @param ValueOffset Offset value
40# @param TypeName    DataType Name
41# @param Value       Value String
42# @param MaxSize     MaxSize value
43#
44# @retval 0     File is updated successfully.
45# @retval not 0 File is updated failed.
46#
47def PatchBinaryFile(FileName, ValueOffset, TypeName, ValueString, MaxSize=0):
48    #
49    # Length of Binary File
50    #
51    FileHandle = open(FileName, 'rb')
52    FileHandle.seek (0, 2)
53    FileLength = FileHandle.tell()
54    FileHandle.close()
55    #
56    # Unify string to upper string
57    #
58    TypeName = TypeName.upper()
59    #
60    # Get PCD value data length
61    #
62    ValueLength = 0
63    if TypeName == 'BOOLEAN':
64        ValueLength = 1
65    elif TypeName == 'UINT8':
66        ValueLength = 1
67    elif TypeName == 'UINT16':
68        ValueLength = 2
69    elif TypeName == 'UINT32':
70        ValueLength = 4
71    elif TypeName == 'UINT64':
72        ValueLength = 8
73    elif TypeName == 'VOID*':
74        if MaxSize == 0:
75            return OPTION_MISSING, "PcdMaxSize is not specified for VOID* type PCD."
76        ValueLength = int(MaxSize)
77    else:
78        return PARAMETER_INVALID, "PCD type %s is not valid." % (CommandOptions.PcdTypeName)
79    #
80    # Check PcdValue is in the input binary file.
81    #
82    if ValueOffset + ValueLength > FileLength:
83        return PARAMETER_INVALID, "PcdOffset + PcdMaxSize(DataType) is larger than the input file size."
84    #
85    # Read binary file into array
86    #
87    FileHandle = open(FileName, 'rb')
88    ByteArray = array.array('B')
89    ByteArray.fromfile(FileHandle, FileLength)
90    FileHandle.close()
91    OrigByteList = ByteArray.tolist()
92    ByteList = ByteArray.tolist()
93    #
94    # Clear the data in file
95    #
96    for Index in range(ValueLength):
97        ByteList[ValueOffset + Index] = 0
98    #
99    # Patch value into offset
100    #
101    SavedStr = ValueString
102    ValueString = ValueString.upper()
103    ValueNumber = 0
104    if TypeName == 'BOOLEAN':
105        #
106        # Get PCD value for BOOLEAN data type
107        #
108        try:
109            if ValueString == 'TRUE':
110                ValueNumber = 1
111            elif ValueString == 'FALSE':
112                ValueNumber = 0
113            elif ValueString.startswith('0X'):
114                ValueNumber = int (ValueString, 16)
115            else:
116                ValueNumber = int (ValueString)
117            if ValueNumber != 0:
118                ValueNumber = 1
119        except:
120            return PARAMETER_INVALID, "PCD Value %s is not valid dec or hex string." % (ValueString)
121        #
122        # Set PCD value into binary data
123        #
124        ByteList[ValueOffset] = ValueNumber
125    elif TypeName in ['UINT8', 'UINT16', 'UINT32', 'UINT64']:
126        #
127        # Get PCD value for UINT* data type
128        #
129        try:
130            if ValueString.startswith('0X'):
131                ValueNumber = int (ValueString, 16)
132            else:
133                ValueNumber = int (ValueString)
134        except:
135            return PARAMETER_INVALID, "PCD Value %s is not valid dec or hex string." % (ValueString)
136        #
137        # Set PCD value into binary data
138        #
139        for Index in range(ValueLength):
140            ByteList[ValueOffset + Index] = ValueNumber % 0x100
141            ValueNumber = ValueNumber / 0x100
142    elif TypeName == 'VOID*':
143        ValueString = SavedStr
144        if ValueString.startswith('L"'):
145            #
146            # Patch Unicode String
147            #
148            Index = 0
149            for ByteString in ValueString[2:-1]:
150                #
151                # Reserve zero as unicode tail
152                #
153                if Index + 2 >= ValueLength:
154                    break
155                #
156                # Set string value one by one
157                #
158                ByteList[ValueOffset + Index] = ord(ByteString)
159                Index = Index + 2
160        elif ValueString.startswith("{") and ValueString.endswith("}"):
161            #
162            # Patch {0x1, 0x2, ...} byte by byte
163            #
164            ValueList = ValueString[1 : len(ValueString) - 1].split(', ')
165            Index = 0
166            try:
167                for ByteString in ValueList:
168                    if ByteString.upper().startswith('0X'):
169                        ByteValue = int(ByteString, 16)
170                    else:
171                        ByteValue = int(ByteString)
172                    ByteList[ValueOffset + Index] = ByteValue % 0x100
173                    Index = Index + 1
174                    if Index >= ValueLength:
175                        break
176            except:
177                return PARAMETER_INVALID, "PCD Value %s is not valid dec or hex string array." % (ValueString)
178        else:
179            #
180            # Patch ascii string
181            #
182            Index = 0
183            for ByteString in ValueString[1:-1]:
184                #
185                # Reserve zero as string tail
186                #
187                if Index + 1 >= ValueLength:
188                    break
189                #
190                # Set string value one by one
191                #
192                ByteList[ValueOffset + Index] = ord(ByteString)
193                Index = Index + 1
194    #
195    # Update new data into input file.
196    #
197    if ByteList != OrigByteList:
198        ByteArray = array.array('B')
199        ByteArray.fromlist(ByteList)
200        FileHandle = open(FileName, 'wb')
201        ByteArray.tofile(FileHandle)
202        FileHandle.close()
203    return 0, "Patch Value into File %s successfully." % (FileName)
204
205## Parse command line options
206#
207# Using standard Python module optparse to parse command line option of this tool.
208#
209# @retval Options   A optparse.Values object containing the parsed options
210# @retval InputFile Path of file to be trimmed
211#
212def Options():
213    OptionList = [
214        make_option("-f", "--offset", dest="PcdOffset", action="store", type="int",
215                          help="Start offset to the image is used to store PCD value."),
216        make_option("-u", "--value", dest="PcdValue", action="store",
217                          help="PCD value will be updated into the image."),
218        make_option("-t", "--type", dest="PcdTypeName", action="store",
219                          help="The name of PCD data type may be one of VOID*,BOOLEAN, UINT8, UINT16, UINT32, UINT64."),
220        make_option("-s", "--maxsize", dest="PcdMaxSize", action="store", type="int",
221                          help="Max size of data buffer is taken by PCD value.It must be set when PCD type is VOID*."),
222        make_option("-v", "--verbose", dest="LogLevel", action="store_const", const=EdkLogger.VERBOSE,
223                          help="Run verbosely"),
224        make_option("-d", "--debug", dest="LogLevel", type="int",
225                          help="Run with debug information"),
226        make_option("-q", "--quiet", dest="LogLevel", action="store_const", const=EdkLogger.QUIET,
227                          help="Run quietly"),
228        make_option("-?", action="help", help="show this help message and exit"),
229    ]
230
231    # use clearer usage to override default usage message
232    UsageString = "%prog -f Offset -u Value -t Type [-s MaxSize] <input_file>"
233
234    Parser = OptionParser(description=__copyright__, version=__version__, option_list=OptionList, usage=UsageString)
235    Parser.set_defaults(LogLevel=EdkLogger.INFO)
236
237    Options, Args = Parser.parse_args()
238
239    # error check
240    if len(Args) == 0:
241        EdkLogger.error("PatchPcdValue", PARAMETER_INVALID, ExtraData=Parser.get_usage())
242
243    InputFile = Args[len(Args) - 1]
244    return Options, InputFile
245
246## Entrance method
247#
248# This method mainly dispatch specific methods per the command line options.
249# If no error found, return zero value so the caller of this tool can know
250# if it's executed successfully or not.
251#
252# @retval 0     Tool was successful
253# @retval 1     Tool failed
254#
255def Main():
256    try:
257        #
258        # Check input parameter
259        #
260        EdkLogger.Initialize()
261        CommandOptions, InputFile = Options()
262        if CommandOptions.LogLevel < EdkLogger.DEBUG_9:
263            EdkLogger.SetLevel(CommandOptions.LogLevel + 1)
264        else:
265            EdkLogger.SetLevel(CommandOptions.LogLevel)
266        if not os.path.exists (InputFile):
267            EdkLogger.error("PatchPcdValue", FILE_NOT_FOUND, ExtraData=InputFile)
268            return 1
269        if CommandOptions.PcdOffset == None or CommandOptions.PcdValue == None or CommandOptions.PcdTypeName == None:
270            EdkLogger.error("PatchPcdValue", OPTION_MISSING, ExtraData="PcdOffset or PcdValue of PcdTypeName is not specified.")
271            return 1
272        if CommandOptions.PcdTypeName.upper() not in ["BOOLEAN", "UINT8", "UINT16", "UINT32", "UINT64", "VOID*"]:
273            EdkLogger.error("PatchPcdValue", PARAMETER_INVALID, ExtraData="PCD type %s is not valid." % (CommandOptions.PcdTypeName))
274            return 1
275        if CommandOptions.PcdTypeName.upper() == "VOID*" and CommandOptions.PcdMaxSize == None:
276            EdkLogger.error("PatchPcdValue", OPTION_MISSING, ExtraData="PcdMaxSize is not specified for VOID* type PCD.")
277            return 1
278        #
279        # Patch value into binary image.
280        #
281        ReturnValue, ErrorInfo = PatchBinaryFile (InputFile, CommandOptions.PcdOffset, CommandOptions.PcdTypeName, CommandOptions.PcdValue, CommandOptions.PcdMaxSize)
282        if ReturnValue != 0:
283            EdkLogger.error("PatchPcdValue", ReturnValue, ExtraData=ErrorInfo)
284            return 1
285        return 0
286    except:
287        return 1
288
289if __name__ == '__main__':
290    r = Main()
291    sys.exit(r)
292