Advertisement
Guest User

Untitled

a guest
Jul 18th, 2020
8,258
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.03 KB | None | 0 0
  1. #!/usr/bin/env python2.7
  2. # (c) flatz
  3.  
  4. import sys, os, struct
  5. import argparse
  6. import re
  7.  
  8. def align_up(x, alignment):
  9.     return (x + (alignment - 1)) & ~(alignment - 1)
  10.  
  11. def align_down(x, alignment):
  12.     return x & ~(alignment - 1)
  13.  
  14. def check_file_magic(f, expected_magic):
  15.     old_offset = f.tell()
  16.     try:
  17.         magic = f.read(len(expected_magic))
  18.     except:
  19.         return False
  20.     finally:
  21.         f.seek(old_offset)
  22.     return magic == expected_magic
  23.  
  24. def read_cstring(f):
  25.     s = ''
  26.     while True:
  27.         c = f.read(1)
  28.         if not c:
  29.             return False
  30.         if c == '\0':
  31.             break
  32.         s += c
  33.     return s
  34.  
  35. def check_sdk_version(sdk_version):
  36.     if len(sdk_version) != 10:
  37.         return False
  38.     parts = sdk_version.split('.', 2)
  39.     if len(parts) != 3:
  40.         return False
  41.     try:
  42.         lengths = [2, 3, 3]
  43.         for i, n in enumerate(parts):
  44.             if len(n) != lengths[i]:
  45.                 return False
  46.             n = int(n, 10)
  47.     except:
  48.         return False
  49.     return True
  50.  
  51. # SDK version have 001 in "patch" field
  52. def parse_sdk_version(sdk_version):
  53.     major, minor, patch = sdk_version >> 24, (sdk_version >> 12) & 0xFFF, sdk_version & 0xFFF
  54.     return major, minor, patch
  55.  
  56. def stringify_sdk_version(major, minor, patch):
  57.     return '{0:02x}.{1:03x}.{2:03x}'.format(major, minor, patch)
  58.  
  59. def unstringify_sdk_version(sdk_version):
  60.     major, minor, patch = map(lambda x: int(x, 16), sdk_version.split('.', 2))
  61.     return major, minor, patch
  62.  
  63. def build_sdk_version(major, minor, patch):
  64.     sdk_version = ((major & 0xFF) << 24) | ((minor & 0xFFF) << 12) | (patch & 0xFFF)
  65.     return sdk_version
  66.  
  67. class SfoFile(object):
  68.     FMT = '<4sIIII'
  69.  
  70.     MAGIC = '\x00PSF'
  71.  
  72.     FMT_STRING_SPECIAL = 0x004
  73.     FMT_STRING         = 0x204
  74.     FMT_UINT32         = 0x404
  75.  
  76.     class Entry(object):
  77.         FMT = '<HHIII'
  78.  
  79.         def __init__(self):
  80.             self.key_offset = None
  81.             self.format = None
  82.             self.size = None
  83.             self.max_size = None
  84.             self.data_offset = None
  85.  
  86.             self.key = None
  87.             self.value = None
  88.  
  89.         def load(self, f):
  90.             data = f.read(struct.calcsize(SfoFile.Entry.FMT))
  91.             if len(data) != struct.calcsize(SfoFile.Entry.FMT):
  92.                 print('error: unable to read entry')
  93.                 return False
  94.  
  95.             self.key_offset, self.format, self.size, self.max_size, self.data_offset = struct.unpack(SfoFile.Entry.FMT, data)
  96.  
  97.             return True
  98.  
  99.         def save(self, f):
  100.             data = struct.pack(SfoFile.Entry.FMT, self.key_offset, self.format, self.size, self.max_size, self.data_offset)
  101.             if len(data) != struct.calcsize(SfoFile.Entry.FMT):
  102.                 print('error: unable to save entry')
  103.                 return False
  104.             f.write(data)
  105.  
  106.             return True
  107.  
  108.     def __init__(self):
  109.         self.start_offset = None
  110.         self.magic = None
  111.         self.version = None
  112.         self.key_table_offset = None
  113.         self.data_table_offset = None
  114.         self.num_entries = None
  115.         self.entries = None
  116.         self.entry_map = None
  117.  
  118.     def check(self, f):
  119.         old_offset = f.tell()
  120.         try:
  121.             result = check_file_magic(f, SfoFile.MAGIC)
  122.         except:
  123.             return False
  124.         finally:
  125.             f.seek(old_offset)
  126.         return result
  127.  
  128.     def load(self, f):
  129.         self.start_offset = f.tell()
  130.  
  131.         data = f.read(struct.calcsize(SfoFile.FMT))
  132.         if len(data) != struct.calcsize(SfoFile.FMT):
  133.             print('error: unable to read header')
  134.             return False
  135.  
  136.         self.magic, self.version, self.key_table_offset, self.data_table_offset, self.num_entries = struct.unpack(SfoFile.FMT, data)
  137.         self.entries = []
  138.         for i in xrange(self.num_entries):
  139.             entry = SfoFile.Entry()
  140.             if not entry.load(f):
  141.                 return False
  142.             assert entry.max_size >= entry.size
  143.             self.entries.append(entry)
  144.  
  145.         self.entry_map = {}
  146.         for i, entry in enumerate(self.entries):
  147.             f.seek(self.start_offset + self.key_table_offset + entry.key_offset)
  148.             entry.key = read_cstring(f)
  149.             f.seek(self.start_offset + self.data_table_offset + entry.data_offset)
  150.             entry.value = f.read(entry.max_size)
  151.             entry.value = entry.value[:entry.size]
  152.             self.entry_map[entry.key] = entry
  153.  
  154.         return True
  155.  
  156.     def fixup(self):
  157.         self.num_entries = len(self.entries)
  158.  
  159.         self.key_table_offset = struct.calcsize(SfoFile.FMT)
  160.         self.key_table_offset += self.num_entries * struct.calcsize(SfoFile.Entry.FMT)
  161.         offset = 0
  162.         for i, entry in enumerate(self.entries):
  163.             entry.key_offset = offset
  164.             offset += len(entry.key) + 1
  165.  
  166.         self.data_table_offset = self.key_table_offset + align_up(offset, 0x4)
  167.         offset = 0
  168.         for i, entry in enumerate(self.entries):
  169.             entry.data_offset = offset
  170.             assert len(entry.value) <= entry.max_size
  171.             offset += entry.max_size
  172.  
  173.     def save(self, f):
  174.         data = struct.pack(SfoFile.FMT, self.magic, self.version, self.key_table_offset, self.data_table_offset, self.num_entries)
  175.         if len(data) != struct.calcsize(SfoFile.FMT):
  176.             print('error: unable to save header')
  177.             return False
  178.         f.write(data)
  179.  
  180.         for i, entry in enumerate(self.entries):
  181.             if not entry.save(f):
  182.                 return False
  183.  
  184.         for i, entry in enumerate(self.entries):
  185.             f.seek(self.start_offset + self.key_table_offset + entry.key_offset)
  186.             data = entry.key + '\0'
  187.             f.write(data)
  188.             f.seek(self.start_offset + self.data_table_offset + entry.data_offset)
  189.             data = entry.value.ljust(entry.max_size, '\0')
  190.             f.write(data)
  191.  
  192.         return True
  193.  
  194.     def get_entry(self, key):
  195.         return self.entry_map[key] if key in self.entry_map else None
  196.  
  197.     def dump(self):
  198.         for i, entry in enumerate(self.entries):
  199.             assert entry.format in [SfoFile.FMT_STRING_SPECIAL, SfoFile.FMT_STRING, SfoFile.FMT_UINT32]
  200.             if entry.format == SfoFile.FMT_STRING_SPECIAL:
  201.                 value = entry.value[:entry.size]
  202.             elif entry.format == SfoFile.FMT_STRING:
  203.                 value = entry.value[:entry.size - 1]
  204.             elif entry.format == SfoFile.FMT_UINT32:
  205.                 assert entry.size in [1, 2, 4, 8]
  206.                 if entry.size == struct.calcsize('B'):
  207.                     value = '0x{0:02X}'.format(struct.unpack('<B', entry.value)[0])
  208.                 elif entry.size == struct.calcsize('H'):
  209.                     value = '0x{0:04X}'.format(struct.unpack('<H', entry.value)[0])
  210.                 elif entry.size == struct.calcsize('I'):
  211.                     value = '0x{0:08X}'.format(struct.unpack('<I', entry.value)[0])
  212.                 elif entry.size == struct.calcsize('Q'):
  213.                     value = '0x{0:016X}'.format(struct.unpack('<Q', entry.value)[0])
  214.             print('{0} = {1}'.format(entry.key, value))
  215.  
  216. class MyParser(argparse.ArgumentParser):
  217.     def error(self, message):
  218.         self.print_help()
  219.         sys.stderr.write('\nerror: {0}\n'.format(message))
  220.         sys.exit(2)
  221.  
  222. parser = MyParser(description='sfo tool')
  223. parser.add_argument('input', type=str, help='old file')
  224. parser.add_argument('output', type=str, help='new file')
  225. parser.add_argument('--verbose', action='store_true', default=False, help='show details')
  226. parser.add_argument('--dump', action='store_true', default=False, help='dump entries')
  227. parser.add_argument('--sdk-version', type=str, default=None, help='needed sdk version')
  228. parser.add_argument('--system-version', type=str, default=None, help='needed sdk version')
  229.  
  230. if len(sys.argv) == 1:
  231.     parser.print_usage()
  232.     sys.exit(1)
  233.  
  234. args = parser.parse_args()
  235.  
  236. input_file_path = args.input
  237. if not os.path.isfile(input_file_path):
  238.     parser.error('invalid input file: {0}'.format(input_file_path))
  239.  
  240. output_file_path = args.output
  241. if os.path.exists(output_file_path) and not os.path.isfile(output_file_path):
  242.     parser.error('invalid output file: {0}'.format(output_file_path))
  243.  
  244. if args.sdk_version and not check_sdk_version(args.sdk_version):
  245.     parser.error('bad sdk version')
  246. if args.system_version and not check_sdk_version(args.system_version):
  247.     parser.error('bad system version')
  248.  
  249. if args.verbose:
  250.     print('reading sfo file: {0}'.format(input_file_path))
  251. with open(input_file_path, 'rb') as f:
  252.     sfo = SfoFile()
  253.     if not sfo.check(f):
  254.         print('error: invalid sfo file format')
  255.         sys.exit(1)
  256.     if not sfo.load(f):
  257.         print('error: unable to load sfo file')
  258.         sys.exit(1)
  259.  
  260. if args.sdk_version is not None and 'PUBTOOLINFO' in sfo.entry_map:
  261.     entry = sfo.entry_map['PUBTOOLINFO']
  262.     assert entry.format == SfoFile.FMT_STRING
  263.     sdk_ver_regexp = re.compile(r'sdk_ver=(\d{8})')
  264.     matches = sdk_ver_regexp.search(entry.value)
  265.     if matches is not None:
  266.         start_pos, end_pos = matches.span(1)
  267.         old_sdk_version = int(entry.value[start_pos:end_pos], 16)
  268.         major, minor, patch = parse_sdk_version(old_sdk_version)
  269.         old_sdk_version_str = stringify_sdk_version(major, minor, patch)
  270.         if args.verbose:
  271.             print('sdk version: {0}'.format(old_sdk_version_str))
  272.         major, minor, patch = unstringify_sdk_version(args.sdk_version)
  273.         new_sdk_version = build_sdk_version(major, minor, patch)
  274.         new_sdk_version_str = stringify_sdk_version(major, minor, patch)
  275.         if old_sdk_version > new_sdk_version:
  276.             print('fixing sdk version...')
  277.             if args.verbose:
  278.                 print('wanted sdk version: {0}'.format(new_sdk_version_str))
  279.             new_sdk_version = '{0:08X}'.format(new_sdk_version)
  280.             assert len(new_sdk_version) == (end_pos - start_pos)
  281.             entry.value = entry.value[:start_pos] + new_sdk_version + entry.value[end_pos:]
  282.  
  283. if args.system_version is not None and 'SYSTEM_VER' in sfo.entry_map:
  284.     if not check_sdk_version(args.system_version):
  285.         parser.error('error: bad sdk version')
  286.     entry = sfo.entry_map['SYSTEM_VER']
  287.     assert entry.format == SfoFile.FMT_UINT32 and entry.size == struct.calcsize('I')
  288.     old_system_version, = struct.unpack('<I', entry.value)
  289.     major, minor, patch = parse_sdk_version(old_system_version)
  290.     old_system_version_str = stringify_sdk_version(major, minor, patch)
  291.     if args.verbose:
  292.         print('system version: {0}'.format(old_system_version_str))
  293.     major, minor, patch = unstringify_sdk_version(args.system_version)
  294.     new_system_version = build_sdk_version(major, minor, patch)
  295.     new_system_version_str = stringify_sdk_version(major, minor, patch)
  296.     if old_system_version > new_system_version:
  297.         print('fixing system version...')
  298.         if args.verbose:
  299.             print('wanted system version: {0}'.format(new_system_version_str))
  300.         entry.value = struct.pack('<I', new_system_version)
  301.  
  302. if args.verbose:
  303.     print('recalculating offsets...')
  304. sfo.fixup()
  305.  
  306. if args.dump:
  307.     print('dumping entries...')
  308.     sfo.dump()
  309.  
  310. if args.verbose:
  311.     print('writing sfo file: {0}'.format(output_file_path))
  312. with open(output_file_path, 'wb') as f:
  313.     if not sfo.save(f):
  314.         print('error: unable to save sfo file')
  315.         sys.exit(1)
  316.  
  317. if args.verbose:
  318.     print('done')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement