├── LICENSE ├── README.md └── bmp.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CLI substitution of Intel BMP(Binary Modification Program), written in Python. 2 | -------------------------------------------------------------------------------- /bmp.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from io import BytesIO, StringIO 3 | import argparse 4 | import sys 5 | 6 | 7 | class ssf_dat_struct: 8 | 9 | def __init__(self, value, ptr, size, offset): 10 | self.value = value 11 | self.ptr = ptr 12 | self.size = size 13 | self.offset = offset 14 | 15 | 16 | def infix2postfix(op, dat_struct): 17 | res = [] 18 | stack = ['#'] 19 | isp = { 20 | '==': 5, 21 | '<': 5, 22 | '>': 5, 23 | '!=': 5, 24 | '<=': 5, 25 | '>=': 5, 26 | '||': 3, 27 | '&&': 3, 28 | '(': 1, 29 | ')': 6, 30 | '#': 0 31 | } 32 | icp = { 33 | '==': 4, 34 | '<': 4, 35 | '>': 4, 36 | '!=': 4, 37 | '<=': 4, 38 | '>=': 4, 39 | '||': 2, 40 | '&&': 2, 41 | '(': 6, 42 | ')': 1, 43 | '#': 0 44 | } 45 | i = 1 46 | while i < len(op): 47 | if op[i].startswith('$'): 48 | if dat_struct.get(op[i]): 49 | temp_value = int.from_bytes(dat_struct[op[i]].value, 50 | byteorder='little', 51 | signed=False) 52 | res.append(temp_value) 53 | i += 1 54 | elif op[i].isdigit() or op[i].startswith('0x'): 55 | res.append(int(op[i])) 56 | i += 1 57 | else: 58 | if isp[stack[-1]] < icp[op[i]]: 59 | stack.append(op[i]) 60 | i += 1 61 | elif isp[stack[-1]] > icp[op[i]]: 62 | res.append(stack.pop()) 63 | else: 64 | stack.pop() 65 | i += 1 66 | while stack[-1] != '#': 67 | res.append(stack.pop()) 68 | return res 69 | 70 | 71 | def calculate(res): 72 | stack = [] 73 | for item in res: 74 | if isinstance(item, int): 75 | stack.append(item) 76 | else: 77 | right = stack.pop() 78 | left = stack.pop() 79 | ans = 0 80 | if item == '==': 81 | if left == right: 82 | ans = 1 83 | elif item == '!=': 84 | if left != right: 85 | ans = 1 86 | elif item == '>=': 87 | if left >= right: 88 | ans = 1 89 | elif item == '<=': 90 | if left <= right: 91 | ans = 1 92 | elif item == '>': 93 | if left > right: 94 | ans = 1 95 | elif item == '<': 96 | if left < right: 97 | ans = 1 98 | elif item == '||': 99 | ans = left | right 100 | elif item == '&&': 101 | ans = left & right 102 | stack.append(ans) 103 | return stack[0] 104 | 105 | 106 | def bsf_line_process_read(bsf_line, offset_byte, offset_bit, dat_block_io, 107 | dat_struct): 108 | op = bsf_line.split() 109 | if op[0].startswith('$'): 110 | dat_block_io.seek(offset_byte) 111 | if op[2].startswith('bit'): 112 | bytes_quantity = -(-int(op[1]) // 8) 113 | value_temp = int.from_bytes(dat_block_io.read(bytes_quantity), 114 | byteorder='little', 115 | signed=False) 116 | left_move = 8 * bytes_quantity - offset_bit - int(op[1]) 117 | right_move = 8 * bytes_quantity - int(op[1]) 118 | bytes_move_temp = b'\xFF' * bytes_quantity 119 | bytes_move_temp = int.from_bytes(bytes_move_temp, 120 | byteorder='little', 121 | signed=False) 122 | value_temp = (value_temp << left_move) & bytes_move_temp 123 | value_temp = (value_temp >> right_move) & bytes_move_temp 124 | value_temp = value_temp.to_bytes(length=bytes_quantity, 125 | byteorder='little', 126 | signed=False) 127 | offset_bit += int(op[1]) 128 | if offset_bit >= 8: 129 | offset_byte += offset_bit // 8 130 | offset_bit = offset_bit % 8 131 | dat_struct_temp = ssf_dat_struct(value_temp, 0, 0, 0) 132 | dat_struct[op[0]] = dat_struct_temp 133 | elif op[2].startswith('byte'): 134 | value_temp = dat_block_io.read(int(op[1])) 135 | offset_byte += int(op[1]) 136 | dat_struct_temp = ssf_dat_struct(value_temp, 0, 0, 0) 137 | dat_struct[op[0]] = dat_struct_temp 138 | elif op[3] == 'Offset': 139 | dat_struct_temp = ssf_dat_struct(0, op[1].split(',')[0], 140 | op[2].split(',')[0], op[4]) 141 | dat_struct[op[0].split(',')[0]] = dat_struct_temp 142 | elif op[0] == 'SKIP': 143 | if op[2].startswith('bit'): 144 | offset_bit += int(op[1]) 145 | if offset_bit >= 8: 146 | offset_byte += offset_bit // 8 147 | offset_bit = offset_bit % 8 148 | elif op[2].startswith('byte'): 149 | offset_byte += int(op[1]) 150 | elif op[0] == 'ALIGN': 151 | if offset_bit != 0: 152 | offset_byte += 1 153 | offset_bit = 0 154 | return offset_byte, offset_bit 155 | 156 | 157 | def bsf_line_process_write(bsf_line, offset_byte, offset_bit, dat_block_io, 158 | dat_struct): 159 | op = bsf_line.split() 160 | if op[0].startswith('$'): 161 | if op[2].startswith('bit'): 162 | if dat_struct.get(op[0]): 163 | dat_block_io.seek(offset_byte) 164 | bytes_quantity = -(-int(op[1]) // 8) 165 | temp_for_clear_bits = b'\xFF' * bytes_quantity 166 | temp_for_clear_bits = int.from_bytes(temp_for_clear_bits, 167 | byteorder='little', 168 | signed=False) 169 | for i in range(0, int(op[1])): 170 | temp_for_clear_bits &= ~(1 << (offset_bit + i)) 171 | temp_for_original_bytes = int.from_bytes( 172 | dat_block_io.read(bytes_quantity), 173 | byteorder='little', 174 | signed=False) 175 | temp_for_write = temp_for_clear_bits & temp_for_original_bytes 176 | temp_for_value = int.from_bytes(dat_struct[op[0]].value, 177 | byteorder='little', 178 | signed=False) 179 | temp_for_value = temp_for_value << offset_bit 180 | temp_for_write |= temp_for_value 181 | temp_for_write = temp_for_write.to_bytes(bytes_quantity, 182 | byteorder='little', 183 | signed=False) 184 | dat_block_io.seek(offset_byte) 185 | dat_block_io.write(temp_for_write) 186 | offset_bit += int(op[1]) 187 | if offset_bit >= 8: 188 | offset_byte += offset_bit // 8 189 | offset_bit = offset_bit % 8 190 | elif op[2].startswith('byte'): 191 | if dat_struct.get(op[0]): 192 | dat_block_io.seek(offset_byte) 193 | dat_temp = b'\x00' * int(op[1]) 194 | dat_block_io.write(dat_temp) 195 | dat_block_io.seek(offset_byte) 196 | dat_temp = dat_struct[op[0]].value 197 | dat_block_io.write(dat_temp) 198 | offset_byte += int(op[1]) 199 | elif op[3] == 'Offset': 200 | temp_key = op[0].split(',')[0] 201 | if dat_struct.get(temp_key): 202 | dat_block_io.seek(offset_byte) 203 | dat_struct[temp_key].offset = op[4] 204 | dat_struct[temp_key].ptr = op[1].split(',')[0] 205 | dat_struct[temp_key].size = op[2].split(',')[0] 206 | elif op[0] == 'SKIP': 207 | if op[2].startswith('bit'): 208 | offset_bit += int(op[1]) 209 | if offset_bit >= 8: 210 | offset_byte += offset_bit // 8 211 | offset_bit = offset_bit % 8 212 | elif op[2].startswith('byte'): 213 | offset_byte += int(op[1]) 214 | elif op[0] == 'ALIGN': 215 | if offset_bit != 0: 216 | offset_byte += 1 217 | offset_bit = 0 218 | return offset_byte, offset_bit 219 | 220 | 221 | def save_ssf(dat_raw, bsf_io): 222 | dat_struct = dict() 223 | bsf_lines_struct = [] 224 | while True: 225 | current_line = bsf_io.readline() 226 | if current_line.startswith('StructDef') is not True: 227 | pass 228 | else: 229 | current_line = bsf_io.readline() 230 | break 231 | while True: 232 | if current_line.startswith('EndStruct'): 233 | break 234 | else: 235 | bsf_lines_struct.append(current_line) 236 | current_line = bsf_io.readline() 237 | bsf_lines_page = [] 238 | while True: 239 | if current_line == '': 240 | break 241 | else: 242 | bsf_lines_page.append(current_line) 243 | current_line = bsf_io.readline() 244 | offset_byte = 0 245 | offset_bit = 0 246 | for current_line in bsf_lines_struct: 247 | if current_line.isspace() or current_line.split( 248 | )[0] == ';' or current_line.split()[0] == 'Find': 249 | pass 250 | elif current_line.split()[0] == 'Find_Ptr_Ref' and current_line.split( 251 | )[1] == '"BIOS_DATA_BLOCK"': 252 | initial_offset = dat_raw.find(b'BIOS_DATA_BLOCK') + 16 253 | dat_io = BytesIO(dat_raw) 254 | dat_io.seek(initial_offset) 255 | dat_block_io = BytesIO(dat_io.read()) 256 | else: 257 | temp = bsf_line_process_read(current_line, offset_byte, offset_bit, 258 | dat_block_io, dat_struct) 259 | offset_byte = temp[0] 260 | offset_bit = temp[1] 261 | data_type = ['Combo', 'Table', 'EditNum'] 262 | str_type = ['MultiText', 'EditText'] 263 | ssf_lines_raw = [] 264 | no_skip = 1 265 | for current_line in bsf_lines_page: 266 | if current_line.isspace() or current_line.split()[0] == ';': 267 | continue 268 | op = current_line.split() 269 | if op[0].upper() == '#IF': 270 | current_line = current_line.split(';')[0] 271 | current_line = current_line.replace('(', ' ( ').replace(')', ' ) ') 272 | op = current_line.split() 273 | op = infix2postfix(op, dat_struct) 274 | op = calculate(op) 275 | no_skip = op & no_skip 276 | elif op[0].upper() == '#ELSEIF': 277 | current_line = current_line.split(';')[0] 278 | current_line = current_line.replace('(', ' ( ').replace(')', ' ) ') 279 | op = current_line.split() 280 | op = infix2postfix(op, dat_struct) 281 | op = calculate(op) 282 | no_skip = op 283 | elif op[0].upper() == '#ELSE': 284 | no_skip = not no_skip 285 | elif op[0].upper() == '#ENDIF': 286 | no_skip = 1 287 | elif no_skip == 0: 288 | pass 289 | elif op[0] == 'Page': 290 | ssf_lines_raw.append('\n' + 'PAGE ' + current_line.split('"')[1] + 291 | '\n') 292 | elif op[0] in data_type: 293 | if op[0] == 'Combo' or op[0] == 'EditNum': 294 | op2 = op[1].split(',')[0] 295 | if dat_struct.get(op2): 296 | ssf_lines_raw.append(op2 + ' ' + ' '.join( 297 | '{:02X}'.format(b) 298 | for b in dat_struct[op2].value) + '\n') 299 | elif op[0] == 'Table': 300 | if dat_struct.get(op[1]): 301 | if dat_struct[op[1]].ptr != 0: 302 | if dat_struct.get(dat_struct[op[1]].ptr): 303 | table_ptr = int.from_bytes( 304 | dat_struct[dat_struct[op[1]].ptr].value, 305 | byteorder='little', 306 | signed=False) + int( 307 | dat_struct[op[1]].offset) - 16 308 | if dat_struct.get(dat_struct[op[1]].size): 309 | table_size = int.from_bytes( 310 | dat_struct[dat_struct[op[1]].size].value, 311 | byteorder='little', 312 | signed=False) 313 | dat_block_io.seek(table_ptr) 314 | dat_struct[op[1]].value = dat_block_io.read(table_size) 315 | ssf_lines_raw.append('TABLE ' + op[1] + ' ' + ' '.join( 316 | '{:02X}'.format(b) 317 | for b in dat_struct[op[1]].value) + '\n') 318 | elif op[0] in str_type: 319 | op2 = op[1].split(',')[0] 320 | if dat_struct.get(op2): 321 | ssf_lines_raw.append( 322 | 'STRING ' + op2 + ' ' + 323 | dat_struct[op2].value.replace(b'\x0D\x0A', b'\\r\\n'). 324 | decode().strip(b'\x00'.decode()) + '\n') 325 | return ssf_lines_raw 326 | 327 | 328 | def apply_ssf(dat_raw, bsf_io, ssf_io): 329 | ssf_lines = [] 330 | while True: 331 | current_line = ssf_io.readline() 332 | if not current_line: 333 | break 334 | elif current_line.startswith('PAGE') or current_line.isspace(): 335 | pass 336 | else: 337 | ssf_lines.append(current_line) 338 | dat_struct = dict() 339 | for current_line in ssf_lines: 340 | op = current_line.split(' ', 1) 341 | if op[0] == 'STRING': 342 | op = op[1].split(' ', 1) 343 | dat_temp = op[1].split('\n', 1)[0].encode('ASCII').replace( 344 | b'\\r\\n', b'\r\n') 345 | dat_struct_temp = ssf_dat_struct(dat_temp, 0, 0, 0) 346 | dat_struct[op[0]] = dat_struct_temp 347 | elif op[0] == 'TABLE': 348 | op = op[1].split(' ', 1) 349 | dat_temp = bytes.fromhex(op[1].strip()) 350 | dat_struct_temp = ssf_dat_struct(dat_temp, 0, 0, 0) 351 | dat_struct[op[0]] = dat_struct_temp 352 | elif op[0].startswith('$'): 353 | temp = op[1].strip() 354 | if len(temp) == 1: 355 | temp = '0' + temp 356 | dat_temp = bytes.fromhex(temp) 357 | dat_struct_temp = ssf_dat_struct(dat_temp, 0, 0, 0) 358 | dat_struct[op[0]] = dat_struct_temp 359 | bsf_lines_struct = [] 360 | while True: 361 | current_line = bsf_io.readline() 362 | if current_line.startswith('StructDef') is not True: 363 | pass 364 | else: 365 | current_line = bsf_io.readline() 366 | break 367 | while True: 368 | if current_line.startswith('EndStruct'): 369 | break 370 | else: 371 | bsf_lines_struct.append(current_line) 372 | current_line = bsf_io.readline() 373 | bsf_lines_page = [] 374 | while True: 375 | if current_line == '': 376 | break 377 | else: 378 | bsf_lines_page.append(current_line) 379 | current_line = bsf_io.readline() 380 | offset_byte = 0 381 | offset_bit = 0 382 | for current_line in bsf_lines_struct: 383 | if current_line.isspace() or current_line.split( 384 | )[0] == ';' or current_line.split()[0] == 'Find': 385 | pass 386 | elif current_line.split()[0] == 'Find_Ptr_Ref' and current_line.split( 387 | )[1] == '"BIOS_DATA_BLOCK"': 388 | initial_offset = dat_raw.find(b'BIOS_DATA_BLOCK') + 16 389 | dat_io = BytesIO(dat_raw) 390 | dat_io.seek(initial_offset) 391 | dat_block_io = BytesIO(dat_io.read()) 392 | elif '_Ptr' in current_line.split( 393 | )[0] or '_Size' in current_line.split()[0]: 394 | temp = bsf_line_process_read(current_line, offset_byte, offset_bit, 395 | dat_block_io, dat_struct) 396 | offset_byte = temp[0] 397 | offset_bit = temp[1] 398 | else: 399 | temp = bsf_line_process_write(current_line, offset_byte, 400 | offset_bit, dat_block_io, dat_struct) 401 | offset_byte = temp[0] 402 | offset_bit = temp[1] 403 | for current_line in bsf_lines_page: 404 | if current_line.startswith( 405 | ';') or current_line.isspace() or current_line.startswith('$'): 406 | continue 407 | op = current_line.split() 408 | if op[0] == 'Table': 409 | temp_key = op[1] 410 | if dat_struct.get(temp_key): 411 | if dat_struct[temp_key].ptr != 0: 412 | table_ptr = '' 413 | table_size = '' 414 | ptr_key = dat_struct[temp_key].ptr 415 | size_key = dat_struct[temp_key].size 416 | if dat_struct.get(ptr_key): 417 | table_ptr = int.from_bytes( 418 | dat_struct[ptr_key].value, 419 | byteorder='little', 420 | signed=False) + int( 421 | dat_struct[temp_key].offset) - 16 422 | if dat_struct.get(size_key): 423 | table_size = int.from_bytes(dat_struct[size_key].value, 424 | byteorder='little', 425 | signed=False) 426 | if table_ptr == '' and table_size == '': 427 | pass 428 | else: 429 | dat_block_io.seek(table_ptr) 430 | dat_temp = b'\x00' * table_size 431 | dat_block_io.write(dat_temp) 432 | dat_block_io.seek(table_ptr) 433 | dat_block_io.write(dat_struct[temp_key].value) 434 | dat_io.seek(0) 435 | dat_block_io.seek(0) 436 | dat_raw = dat_io.read(initial_offset) + dat_block_io.read() 437 | checksum_offset = dat_raw.find(b'BIOS_DATA_BLOCK') - 22 438 | dat_io = BytesIO(dat_raw) 439 | checksum = 0 440 | dat_io.seek(checksum_offset) 441 | dat_io.write(checksum.to_bytes(1, byteorder='little')) 442 | dat_io.seek(0) 443 | while True: 444 | temp = dat_io.read(1) 445 | if not temp: 446 | break 447 | checksum += int.from_bytes(temp, byteorder='little', signed=False) 448 | checksum &= 0xFF 449 | if checksum == 0: 450 | pass 451 | else: 452 | checksum = 0x100 - checksum 453 | dat_io.seek(checksum_offset) 454 | dat_io.write(checksum.to_bytes(1, byteorder='little')) 455 | dat_io.seek(0) 456 | dat_raw = dat_io.read() 457 | return dat_raw 458 | 459 | 460 | def main(): 461 | parser = argparse.ArgumentParser( 462 | description='CLI substitution of Intel BMP, written in Python.') 463 | parser.add_argument('dat_file', 464 | type=str, 465 | help='path to the binary (.dat) file') 466 | parser.add_argument('bsf_file', 467 | type=str, 468 | help='path to the script (.bsf) file') 469 | parser.add_argument('ssf_file', 470 | type=str, 471 | nargs='?', 472 | help='path to the settings (.ssf) file') 473 | parser.add_argument( 474 | 'new_dat_file', 475 | type=str, 476 | nargs='?', 477 | help= 478 | 'optional output path for the modified binary (modifies original if omitted)' 479 | ) 480 | parser.add_argument('-s', 481 | action='store_true', 482 | help='apply settings from SSF to the binary file') 483 | parser.add_argument( 484 | '-b', 485 | action='store_true', 486 | help='extract settings from the binary into an SSF file') 487 | 488 | args = parser.parse_args() 489 | 490 | script_stem = Path(sys.argv[0]).stem.lower() 491 | 492 | if script_stem == "avsr": 493 | dat_file = Path.cwd().joinpath(args.dat_file.lstrip('\\')) 494 | bsf_file = Path.cwd().joinpath(args.bsf_file.lstrip('\\')) 495 | ssf_file = Path.cwd().joinpath('data', 'tmp', 'settings.ssf') 496 | args.b = True 497 | args.s = False 498 | elif script_stem == "ssf": 499 | dat_file = Path.cwd().joinpath(args.dat_file) 500 | bsf_file = Path.cwd().joinpath('res', 'vbios', args.bsf_file + '.bsf') 501 | ssf_file = Path.cwd().joinpath(args.dat_file + '.ssf') 502 | args.b = True 503 | args.s = False 504 | else: 505 | dat_file = Path.cwd().joinpath(args.dat_file) 506 | bsf_file = Path.cwd().joinpath(args.bsf_file) 507 | if args.ssf_file: 508 | ssf_file = Path.cwd().joinpath(args.ssf_file) 509 | else: 510 | ssf_file = dat_file.with_suffix('.ssf') 511 | 512 | if not (args.s ^ args.b): 513 | parser.error( 514 | "You must specify exactly one of -s (apply) or -b (extract)") 515 | 516 | if not dat_file.is_file(): 517 | parser.error('invalid dat file.') 518 | 519 | if not bsf_file.is_file(): 520 | parser.error('invalid bsf file.') 521 | 522 | if args.s and not ssf_file.is_file(): 523 | parser.error('invalid ssf file.') 524 | 525 | if args.s: 526 | with open(str(dat_file), 'rb') as f: 527 | dat_raw = f.read() 528 | with open(str(bsf_file), encoding='utf-8', errors='ignore', 529 | mode='r') as f: 530 | bsf_io = StringIO(f.read()) 531 | with open(str(ssf_file), encoding='utf-8', errors='ignore', 532 | mode='r') as f: 533 | ssf_io = StringIO(f.read()) 534 | dat_raw = apply_ssf(dat_raw, bsf_io, ssf_io) 535 | output_file = Path.cwd().joinpath( 536 | args.new_dat_file) if args.new_dat_file else dat_file 537 | with open(str(output_file), 'wb') as f: 538 | f.write(dat_raw) 539 | 540 | elif args.b: 541 | with open(str(dat_file), 'rb') as f: 542 | dat_raw = f.read() 543 | with open(str(bsf_file), encoding='utf-8', errors='ignore', 544 | mode='r') as f: 545 | bsf_io = StringIO(f.read()) 546 | ssf_raw = save_ssf(dat_raw, bsf_io) 547 | with open(str(ssf_file), encoding='utf-8', mode='w', 548 | newline='\r\n') as f: 549 | f.writelines(ssf_raw) 550 | 551 | 552 | if __name__ == "__main__": 553 | main() 554 | --------------------------------------------------------------------------------