# -*- coding: utf8 -*- # libray - Libre Blu-Ray PS3 ISO Tool # Copyright © 2018 - 2024 Nichlas Severinsen # # This file is part of libray. # # libray is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # libray is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with libray. If not, see . import os import sys import zlib import shutil import tempfile try: from libray import core except ImportError: import core class IRD: """Class for handling .ird files Attributes: version: IRD version number game_id: PS3 game identifier game_name: Name of PS3 game update_version: PS3 firmware update version game_version: PS3 game version app_version: PS3 app version region_count: How many encrypted regions are in the .iso file_count: How many files are supposed to be in the .iso data1: Encryption key """ ORDER = 'little' MAGIC_STRING = b'3IRD' def __init__(self, ird_path, verbose=False): """IRD constructor using args from argparse.""" temp_path = self._prepare_temp(ird_path) try: self.size = core.size(temp_path) if not self.size: core.error('IRD file is empty!') with open(temp_path, 'rb') as input_ird: if input_ird.read(4) != self.MAGIC_STRING: core.error('Either not an IRD file, corruped IRD file, or unknown IRD format') self.version = core.to_int(input_ird.read(1), self.ORDER) self.game_id = input_ird.read(9) name_length = core.read_seven_bit_encoded_int(input_ird, self.ORDER) self.game_name = input_ird.read(name_length).decode('utf8') self.update_version = input_ird.read(4) self.game_version = input_ird.read(5) self.app_version = input_ird.read(5) if self.version == 7: self.identifier = input_ird.read(4) header_length = (core.to_int(input_ird.read(4), self.ORDER)) self.header = input_ird.read(header_length) footer_length = (core.to_int(input_ird.read(4), self.ORDER)) self.footer = input_ird.read(footer_length) self.region_count = core.to_int(input_ird.read(1), self.ORDER) self.region_hashes = [] for _ in range(0, self.region_count): self.region_hashes.append(input_ird.read(16)) self.file_count = core.to_int(input_ird.read(4), self.ORDER) self.file_hashes = [] for _ in range(0, self.file_count): key = core.to_int(input_ird.read(8), self.ORDER) val = input_ird.read(16) self.file_hashes.append({'key': key, 'val': val}) if self.version >= 9: self.pic = input_ird.read(115) input_ird.seek(input_ird.tell() + 4) # ? self.data1 = input_ird.read(16) self.data2 = input_ird.read(16) if self.version < 9: self.pic = input_ird.read(115) if self.version < 7: self.uid = core.to_int(input_ird.read(4), self.ORDER) if verbose: self.print_info() finally: os.remove(temp_path) def _prepare_temp(self, filename): """Decompress (if needed) and copy an IRD file to a unique temp file. Returns the path to the temp file. The caller is responsible for deleting it (use a ``try / finally`` block). """ with open(filename, 'rb') as f: magic = f.read(4) fd, tmp_path = tempfile.mkstemp(suffix='.ird') os.close(fd) if magic != self.MAGIC_STRING: # Compressed — decompress to the temp file with open(filename, 'rb') as gzfile, open(tmp_path, 'wb') as tmpfile: tmpfile.write(zlib.decompress(gzfile.read(), zlib.MAX_WBITS | 16)) else: # Uncompressed — copy to the temp file shutil.copyfile(filename, tmp_path) return tmp_path def print_info(self, regions=False): # TODO: This could probably have been a __str__? Who cares? """Print some info about the IRD.""" print('Info from IRD:') print('Version: %s' % self.version) print('Game ID: %s' % self.game_id) print('Game Name: %s' % self.game_name) print('Update Version: %s' % self.update_version) print('Game Version: %s' % self.game_version) print('App Version: %s' % self.app_version) print('Region Count: %s' % self.region_count) if regions: for i, region_hash in enumerate(self.region_hashes): print('\tRegion Hash %s: %s' % (i, region_hash.hex())) print('File Count: %s' % self.file_count) print('Data1: %s' % self.data1.hex()) print('Data2: %s' % self.data2.hex())