Files
base/tools/sdtools/pactool/msfstool.py
2025-11-07 09:57:14 +08:00

350 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import struct
import traceback
import argparse
class MULTI_SFS_HEADER_T(object):
MAGIC = 0x5346534D # which means "MSFS"
VERSION = 0
FORMAT_STRING = "<4I16x"
def __init__(self):
self.magic = self.MAGIC # 0x5346534D
self.version = self.VERSION # structure version
self.fs_count = "" # flash sequence count
self.fs_offset = struct.calcsize(self.FORMAT_STRING) # flash sequence offset, default is 32
self.reserved = "" # reserved 16 bytes
class FLASH_SEQ_INFO_T(object):
VERSION = 0
FORMAT_STRING = "<3IQ12x"
def __init__(self):
self.version = self.VERSION # structure version
self.offset = 0 # data offset
self.size = 0 # data size
self.flash_id = 0 # Memory Type (1 Byte) <<8 | Manufacturer ID (1 Byte),e.g. "gd25q32/16" -> 0x40C8
self.reserved = "" # reserved 16 bytes
# image data
self.image_data = None
class PackError(Exception):
"""Application-specific errors.
These errors represent issues for which a stack-trace should not be
presented.
Attributes:
message: Error message.
"""
def __init__(self, message):
self.message = message
Exception.__init__(self, message)
class SaveImage(object):
def __init__(self, filename):
self.file = None
self.filename = filename
self._initialize()
def _initialize(self):
self.file = open(self.filename, 'wb+')
def write(self, data):
if self.file:
self.file.write(data)
else:
raise Exception("Failed to create binary file.")
def __del__(self):
if self.file:
self.file.flush()
self.file.close()
class ReadImage(object):
def __init__(self, image_filename):
"""Initializes an image handler.
Arguments:
image_filename: The name of the file to operate on.
Raises:
ValueError: If data in the file is invalid.
"""
self.file = None
self.image_size = 0
self._file_pos = 0
self.filename = image_filename
self._initialize()
def _initialize(self):
try:
self._file_pos = 0
self.file = open(self.filename, 'rb')
self.file.seek(0, os.SEEK_END)
self.image_size = self.file.tell()
self.file.seek(0, os.SEEK_SET)
except IOError as e:
# print("Failed to open %s" % self.filename)
self.file = None
def seek(self, offset):
"""Sets the cursor position for reading from unsparsified file.
Arguments:
offset: Offset to seek to from the beginning of the file.
"""
self._file_pos = offset
def read(self, size):
"""Reads data from the unsparsified file.
This method may return fewer than |size| bytes of data if the end
of the file was encountered.
The file cursor for reading is advanced by the number of bytes
read.
Arguments:
size: Number of bytes to read.
Returns:
The data.
"""
self.file.seek(self._file_pos)
data = self.file.read(size)
self._file_pos += len(data)
return data
def tell(self):
"""Returns the file cursor position for reading from unsparsified file.
Returns:
The file cursor position for reading.
"""
return self._file_pos
class Packet(object):
"""Business logic for msfstool command-line tool."""
def make_msfs_image(self, output, images):
"""Implementation of the 'make_msfs_image' command.
Arguments:
output: Output file where disk image is to be written to.
images: List of partition image paths to be combined (as specified by
bpt). Each element is of the form.
'FLASH_ID@/PATH/TO/SFS_IMAGE'
Raises:
PackError: If application-specific error occurs.
"""
# Create mapping of partition name to partition image file.
image_file_names = {}
try:
if images:
for item in images:
flash_id, path = item.split("@")
flash_id = flash_id.lower()
if 0 == flash_id.find("0x"):
id = int(flash_id, 16)
else:
id = int(flash_id, 10)
image_file_names[id] = path
if not os.path.exists(path):
raise PackError('image file do not exist: {}.'.format(item))
except ValueError as e:
raise PackError('Bad image argument {}.'.format(images))
_ids = []
for k, v in image_file_names.items():
print("Flash sequence info :0x%X : %s" % (k, v))
_ids.append(k)
_ids.sort(reverse=False)
_fs_infos = []
for k in _ids:
_image = ReadImage(image_file_names[k])
if _image.file is None:
raise PackError('Failed to open sfs fie: {}.'.format(image_file_names[k]))
_info = FLASH_SEQ_INFO_T()
_info.version = FLASH_SEQ_INFO_T.VERSION
_info.size = _image.image_size
_info.flash_id = k
_info.image_data = _image.read(_image.image_size)
_fs_infos.append(_info)
# make image
_header = MULTI_SFS_HEADER_T()
_header.fs_count = len(_ids)
_header.fs_offset = struct.calcsize(MULTI_SFS_HEADER_T.FORMAT_STRING)
_header_data = struct.pack(MULTI_SFS_HEADER_T.FORMAT_STRING,
_header.magic,
_header.version,
_header.fs_count,
_header.fs_offset)
# create output image file
_s_file = SaveImage(output)
_s_file.write(_header_data)
# info
offset = struct.calcsize(MULTI_SFS_HEADER_T.FORMAT_STRING) + \
_header.fs_count*struct.calcsize(FLASH_SEQ_INFO_T.FORMAT_STRING)
for item in _fs_infos:
item.offset = offset
_info_data = struct.pack(FLASH_SEQ_INFO_T.FORMAT_STRING,
item.version,
item.offset,
item.size,
item.flash_id)
_s_file.write(_info_data)
offset += item.size
for item in _fs_infos:
_s_file.write(item.image_data)
return True
def extract_msfs(self, output_path, image_file):
output_path = output_path.replace('\\', '/')
os.makedirs(output_path, exist_ok=True)
_image = ReadImage(image_file)
if _image.file is None:
raise PackError('Failed to open msfs fie: {}.'.format(image_file))
if _image.image_size < struct.calcsize(MULTI_SFS_HEADER_T.FORMAT_STRING):
raise PackError('Invalid size of the msfs fie: {}.'.format(image_file))
_header_bin = _image.read(struct.calcsize(MULTI_SFS_HEADER_T.FORMAT_STRING))
_header = MULTI_SFS_HEADER_T()
(_header.magic,
_header.version,
_header.fs_count,
_header.fs_offset) = struct.unpack(MULTI_SFS_HEADER_T.FORMAT_STRING, _header_bin)
if not _header.magic == MULTI_SFS_HEADER_T.MAGIC:
raise PackError('Invalid msfs fie: {}.'.format(image_file))
print("msfs summary information :")
print(" magic: 0x%X" % _header.magic)
print(" version: 0x%X" % _header.version)
print(" fs count: 0x%X" % _header.fs_count)
print(" fs offset: 0x%X" % _header.fs_offset)
_fs_infos = []
for i in range(_header.fs_count):
_info_bin = _image.read(struct.calcsize(FLASH_SEQ_INFO_T.FORMAT_STRING))
_info = FLASH_SEQ_INFO_T()
(_info.version,
_info.offset,
_info.size,
_info.flash_id) = struct.unpack(FLASH_SEQ_INFO_T.FORMAT_STRING, _info_bin)
_fs_infos.append(_info)
for item in _fs_infos:
_sfs = "%s/FlashID_0x%X.img" % (output_path, item.flash_id)
_file = SaveImage(_sfs)
_image.seek(item.offset)
_file.write(_image.read(item.size))
print("\nextracted sfs information : %s" % _sfs)
print(" version: 0x%X" % item.version)
print(" offset: 0x%X" % item.offset)
print(" size: 0x%X" % item.size)
print(" flash id: 0x%X" % item.flash_id)
return True
class MsfsTool(object):
"""Object for command-line tool."""
def __init__(self):
"""Initializer method."""
self.packet = Packet()
pass
def version(self, _):
"""Implements the 'version' sub-command."""
print('1.21.32.1')
def make_msfs(self, args):
"""Implements the 'make_msfs' sub-command."""
if 0 == len(args.output):
print('Option --ouptut parameter is required.')
return False
result = False
try:
result = self.packet.make_msfs_image(args.output, args.image)
except PackError as e:
print('Error : {}'.format(e.message))
finally:
if result:
print('Successfully generated msfs file.')
else:
os.remove(args.output)
print('Failed to generate msfs file.')
return result
def extract_msfs(self, args):
"""Implements the 'extract_msfs' sub-command."""
result = False
try:
result = self.packet.extract_msfs(args.output, args.image)
except PackError as e:
print('Error : {}'.format(e.message))
finally:
if result:
print('Successfully extracted msfs file.')
else:
print('Failed to extract msfs file.')
return result
def run(self, argv):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands')
sub_parser = subparsers.add_parser('version', help='Prints version of MsfsTool.')
sub_parser.set_defaults(func=self.version)
sub_parser = subparsers.add_parser('make_msfs', help='Create multi sfs image file.')
sub_parser.add_argument('--output',
help='Path to image output.',
type=str,
required=True)
sub_parser.add_argument('--image',
help='Flash ID and path to sfs/nfs image file.',
metavar='FLASH_ID@SFS_FILE',
action='append',
required=True)
sub_parser.set_defaults(func=self.make_msfs)
sub_parser = subparsers.add_parser('extract_msfs', help='Extract multi sfs image file.')
sub_parser.add_argument('--output',
help='Path to extract sfs.',
type=str,
required=True)
sub_parser.add_argument('--image',
help='Path to msfs file.',
type=str,
required=True)
sub_parser.set_defaults(func=self.extract_msfs)
args = parser.parse_args(argv[1:])
try:
func = args.func
except AttributeError:
parser.error("invalid arguments")
return func(args)
if __name__ == '__main__':
obj = MsfsTool()
obj.run(sys.argv)