sfo.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # -*- coding: utf8 -*-
  2. # libray - Libre Blu-Ray PS3 ISO Tool
  3. # Copyright © 2018 - 2024 Nichlas Severinsen
  4. #
  5. # This file is part of libray.
  6. #
  7. # libray is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # libray is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with libray. If not, see <https://www.gnu.org/licenses/>.
  19. try:
  20. from libray import core
  21. except ImportError:
  22. import core
  23. class SFO:
  24. """Class for handling .sfo files
  25. Attributes:
  26. magic: Magic header
  27. version: .SFO version
  28. key_table_start: Absolute offset for key_table in .SFO
  29. data_table_start: Absolute offset for index_table in .SFO
  30. tables_entries: Number of entries in index_table and key_table
  31. key_data: Parsed keys and data tables from .SFO transformed into dict
  32. """
  33. def __init__(self, fp):
  34. self.file_start = fp.tell()
  35. # Header
  36. self.magic = fp.read(4)
  37. self.version = fp.read(4)
  38. self.key_table_start = core.to_int(fp.read(4), 'little')
  39. self.data_table_start = core.to_int(fp.read(4), 'little')
  40. self.tables_entries = core.to_int(fp.read(4), 'little')
  41. # Index table
  42. index_table = []
  43. for _ in range(0, self.tables_entries):
  44. index_table.append({
  45. 'key_offset': core.to_int(fp.read(2), 'little'),
  46. 'data_fmt': fp.read(2),
  47. 'data_len': core.to_int(fp.read(4), 'little'),
  48. 'data_max_len': core.to_int(fp.read(4), 'little'),
  49. 'data_offset': core.to_int(fp.read(4), 'little'),
  50. })
  51. # Key table
  52. key_table = []
  53. for i in range(0, self.tables_entries):
  54. # Seek to absolute offset + relative offset of key
  55. fp.seek(self.file_start + self.key_table_start +
  56. index_table[i]['key_offset'])
  57. # Read key string until nullbyte
  58. key = ''
  59. while True:
  60. data = fp.read(1)
  61. if data == b'\x00':
  62. break
  63. key += data.decode('utf8')
  64. key_table.append(key)
  65. # Data table
  66. self.key_data = {}
  67. for i in range(0, self.tables_entries):
  68. # Seek to absolute offset + relative offset of data
  69. fp.seek(self.file_start + self.data_table_start + index_table[i]['data_offset'])
  70. if index_table[i]['data_fmt'] == b'\x04\x02': # UTF8
  71. data = fp.read(index_table[i]['data_len'] - 1).decode('utf8')
  72. elif index_table[i]['data_fmt'] == b'\x04\x04': # int32
  73. data = core.to_int(
  74. fp.read(index_table[i]['data_len']), 'little')
  75. else: # Meh
  76. data = fp.read(index_table[i]['data_len'])
  77. self.key_data[key_table[i]] = data
  78. def __getitem__(self, key):
  79. """Overload [] so we can directly select data using key from .SFO"""
  80. return self.key_data[key]
  81. def print_info(self):
  82. print('Magic:', self.magic)
  83. print('Version: ', self.version)
  84. print('key_table_start:', self.key_table_start)
  85. print('data_table_start:', self.data_table_start)
  86. print('tables_entries:', self.tables_entries)
  87. print(self.key_data)