2018-07-07 00:38:10 +02:00
# -*- coding: utf8 -*-
# libray - Libre Blu-Ray PS3 ISO Tool
2019-08-01 08:42:14 +02:00
# Copyright © 2018 - 2019 Nichlas Severinsen
2019-05-16 10:37:28 +02:00
#
2018-07-07 00:38:10 +02:00
# This file is part of libray.
2019-05-16 10:37:28 +02:00
#
2018-07-07 00:38:10 +02:00
# 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.
2019-05-16 10:37:28 +02:00
#
2018-07-07 00:38:10 +02:00
# 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.
2019-05-16 10:37:28 +02:00
#
2018-07-07 00:38:10 +02:00
# You should have received a copy of the GNU General Public License
# along with libray. If not, see <https://www.gnu.org/licenses/>.
2018-07-07 12:13:11 +02:00
import sys
from tqdm import tqdm
2018-07-07 00:38:10 +02:00
from Crypto . Cipher import AES
2019-06-07 09:00:03 +02:00
2018-07-07 00:38:10 +02:00
try :
from libray import core
2018-07-09 07:40:41 +02:00
from libray import ird
2018-07-07 00:38:10 +02:00
except ImportError :
import core
2018-07-09 07:40:41 +02:00
import ird
2018-07-07 00:38:10 +02:00
class ISO :
2019-11-03 14:49:24 +01:00
""" Class for handling PS3 .iso files.
2019-07-07 21:18:34 +02:00
2019-06-07 09:00:03 +02:00
Attributes :
2019-11-03 14:49:24 +01:00
size : Size of . iso in bytes
number_of_regions : Number of regions in the . iso
regions : List with info of every region
game_id : PS3 game id
ird : IRD object ( see ird . py )
disc_key : data1 from . ird , encrypted
2019-06-07 09:00:03 +02:00
"""
2019-05-16 10:37:28 +02:00
2019-11-03 14:49:24 +01:00
2018-07-07 00:38:10 +02:00
NUM_INFO_BYTES = 4
2019-11-03 14:49:24 +01:00
def read_regions ( self , input_iso , filename ) :
""" List with info dict (start, end, whether it ' s encrypted) for every region.
2020-08-03 19:52:46 +02:00
2019-11-03 14:49:24 +01:00
Basically , every other ( odd numbered ) region is encrypted .
"""
regions = [ ]
encrypted = False
for _ in range ( 0 , self . number_of_regions ) :
2020-08-03 19:52:46 +02:00
2019-11-03 14:49:24 +01:00
regions . append ( {
' start ' : core . to_int ( input_iso . read ( self . NUM_INFO_BYTES ) ) * core . SECTOR ,
' end ' : core . to_int ( input_iso . read ( self . NUM_INFO_BYTES ) ) * core . SECTOR ,
' enc ' : encrypted
} )
input_iso . seek ( input_iso . tell ( ) - self . NUM_INFO_BYTES )
encrypted = not encrypted
# Last region might not actually be 2048 bytes, so we'll just cheat
regions [ - 1 ] [ ' end ' ] = self . size
return regions
2018-07-07 00:38:10 +02:00
def __init__ ( self , args ) :
2019-11-03 14:49:24 +01:00
""" ISO constructor using args from argparse. """
2019-06-07 09:00:03 +02:00
2019-07-07 21:18:34 +02:00
self . size = core . size ( args . iso )
if not self . size :
core . error ( ' looks like ISO file/mount is empty? ' )
2018-07-07 00:38:10 +02:00
with open ( args . iso , ' rb ' ) as input_iso :
2019-11-03 14:49:24 +01:00
# Get number of regions (times two as the number represents both encrypted and decrypted regions )
self . number_of_regions = core . to_int ( input_iso . read ( self . NUM_INFO_BYTES ) ) * 2
# Skip unused bytes
input_iso . seek ( input_iso . tell ( ) + self . NUM_INFO_BYTES )
2018-07-07 00:38:10 +02:00
self . regions = self . read_regions ( input_iso , args . iso )
2019-11-03 14:49:24 +01:00
# Seek to the start of region 2, '+ 16' skips a section containing some 'playstation'
input_iso . seek ( core . SECTOR + 16 )
2018-07-07 19:03:47 +02:00
self . game_id = input_iso . read ( 16 ) . decode ( ' utf8 ' ) . strip ( )
2019-11-03 14:49:24 +01:00
if args . verbose and not args . quiet :
2018-07-07 00:38:10 +02:00
self . print_info ( )
2019-11-03 14:49:24 +01:00
cipher = AES . new ( core . ISO_SECRET , AES . MODE_CBC , core . ISO_IV )
2018-07-09 07:40:41 +02:00
2019-11-03 14:49:24 +01:00
if not args . decryption_key :
if not args . ird :
if not args . quiet :
2020-08-03 19:52:46 +02:00
core . warning ( ' No IRD file specified, finding required file ' )
2019-11-03 14:49:24 +01:00
args . ird = core . ird_by_game_id ( self . game_id ) # Download ird
2018-07-09 07:40:41 +02:00
2019-11-03 14:49:24 +01:00
self . ird = ird . IRD ( args )
2018-07-09 07:40:41 +02:00
2019-11-03 14:49:24 +01:00
if self . ird . region_count != len ( self . regions ) - 1 :
core . error ( ' Corrupt ISO or error in IRD. Expected %s regions, found %s regions ' % ( self . ird . region_count , len ( self . regions ) - 1 ) )
2018-07-07 12:13:11 +02:00
2019-11-03 14:49:24 +01:00
if self . regions [ - 1 ] [ ' start ' ] > self . size :
core . error ( ' Corrupt ISO or error in IRD. Expected filesize larger than %.2f GiB, actual size is %.2f GiB ' % ( self . regions [ - 1 ] [ ' start ' ] / 1024 * * 3 , self . size / 1024 * * 3 ) )
2020-08-03 19:52:46 +02:00
2019-11-03 14:49:24 +01:00
self . disc_key = cipher . encrypt ( self . ird . data1 )
else :
self . disc_key = cipher . encrypt ( core . to_bytes ( args . decryption_key ) )
2018-07-07 00:38:10 +02:00
def decrypt ( self , args ) :
2019-11-03 14:49:24 +01:00
""" Decrypt self using args from argparse. """
2018-07-07 00:38:10 +02:00
2019-11-03 14:49:24 +01:00
if not args . quiet :
print ( ' Decrypting with disc key: %s ' % self . disc_key . hex ( ) )
2018-07-07 00:38:10 +02:00
with open ( args . iso , ' rb ' ) as input_iso :
2020-08-03 19:52:46 +02:00
2019-08-01 08:34:56 +02:00
if not args . output :
output_name = ' %s .iso ' % self . game_id
else :
output_name = args . output
with open ( output_name , ' wb ' ) as output_iso :
2018-07-07 12:13:11 +02:00
2020-08-03 19:52:46 +02:00
if not args . quiet :
pbar = tqdm ( total = ( self . size / / 2048 ) )
2019-05-16 10:37:28 +02:00
2019-11-03 14:49:24 +01:00
for region in self . regions :
2018-07-07 00:38:10 +02:00
input_iso . seek ( region [ ' start ' ] )
2019-11-03 14:49:24 +01:00
# Unencrypted region, just copy it
2018-07-07 00:38:10 +02:00
if not region [ ' enc ' ] :
while input_iso . tell ( ) < region [ ' end ' ] :
data = input_iso . read ( core . SECTOR )
2019-11-03 14:49:24 +01:00
if not data and not args . quiet :
2019-07-07 21:18:34 +02:00
core . warning ( ' Trying to read past the end of the file ' )
2018-07-07 19:03:47 +02:00
break
2018-07-07 00:38:10 +02:00
output_iso . write ( data )
2020-08-03 19:52:46 +02:00
if not args . quiet :
pbar . update ( 1 )
2018-07-07 00:38:10 +02:00
continue
2019-11-03 14:49:24 +01:00
# Encrypted region, decrypt then write
2018-07-07 00:38:10 +02:00
else :
while input_iso . tell ( ) < region [ ' end ' ] :
num = input_iso . tell ( ) / / 2048
iv = bytearray ( [ 0 for i in range ( 0 , 16 ) ] )
for j in range ( 0 , 16 ) :
iv [ 16 - j - 1 ] = ( num & 0xFF )
num >> = 8
2019-05-16 10:37:28 +02:00
2018-07-07 00:38:10 +02:00
data = input_iso . read ( core . SECTOR )
2019-11-03 14:49:24 +01:00
if not data and not args . quiet :
2019-07-07 21:18:34 +02:00
core . warning ( ' Trying to read past the end of the file ' )
2018-07-07 19:03:47 +02:00
break
2019-05-16 10:37:28 +02:00
2018-07-07 00:38:10 +02:00
cipher = AES . new ( self . disc_key , AES . MODE_CBC , bytes ( iv ) )
decrypted = cipher . decrypt ( data )
2019-05-16 10:37:28 +02:00
2018-07-07 00:38:10 +02:00
output_iso . write ( decrypted )
2019-05-16 10:37:28 +02:00
2020-08-03 19:52:46 +02:00
if not args . quiet :
pbar . update ( 1 )
2018-07-07 00:38:10 +02:00
2020-08-03 19:52:46 +02:00
if not args . quiet :
pbar . close ( )
print ( ' Decryption complete. ' )
2018-07-07 00:38:10 +02:00
def print_info ( self ) :
2019-06-07 09:00:03 +02:00
# TODO: This could probably have been a __str__? Who cares?
2019-11-03 14:49:24 +01:00
""" Print some info about the ISO. """
2019-06-07 09:00:03 +02:00
2018-07-07 00:38:10 +02:00
print ( ' Info from ISO: ' )
2019-11-03 14:49:24 +01:00
print ( ' Regions: %s ' % self . number_of_regions )
2018-07-07 00:38:10 +02:00
for i , region in enumerate ( self . regions ) :
print ( i , region )