diff --git a/.gitignore b/.gitignore
index 258a3b4..a04272c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
*.iso
*.ird
*.gz
-ird/
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/README.md b/README.md
index 6cf811e..0fe6d4a 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ extracting, repackaging, and encrypting PS3 ISOs.
A hackable, crossplatform, alternative to ISOTools and ISO-Rebuilder.
+
[see also](http://www.psdevwiki.com/ps3/Bluray_disc#Encryption) ([archive.fo](https://archive.fo/hN1E6))
[7bit encoded int / RLE / CLP](https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/io/binaryreader.cs#L582-L600)
@@ -16,8 +17,16 @@ A hackable, crossplatform, alternative to ISOTools and ISO-Rebuilder.
clp = compressed length prefix
-## Possibly todo
+## Todo
+- Automatically download .ird file if not given
- Docstrings
- Extract ISO (currently doable with `7z x output.iso`
+- Test .irds with version < 9
+- Custom command to backup all irds available
+
+## Advanced
+
+Figure out the SCSI commands to get data1, if at all possible.
+
diff --git a/libray/core.py b/libray/core.py
index 14f38ae..6067943 100644
--- a/libray/core.py
+++ b/libray/core.py
@@ -21,6 +21,8 @@
import os
import sys
import shutil
+import requests
+from bs4 import BeautifulSoup
try:
@@ -31,6 +33,8 @@ except ImportError:
# Magic numbers / Constant variables
SECTOR = 2048
+ALL_IRD_NET_LOC = 'http://jonnysp.bplaced.net/data.php'
+GET_IRD_NET_LOC = 'http://jonnysp.bplaced.net/ird/'
# Utility functions
@@ -76,6 +80,35 @@ def error(msg):
def warning(msg):
print('WARNING: %s. Continuing regardless' % msg)
+
+def download_ird(ird_name):
+ ird_link = GET_IRD_NET_LOC + ird_name
+ r = requests.get(ird_link, stream=True)
+
+ with open(ird_name, 'wb') as ird_file:
+ r.raw.decode_content = True
+ shutil.copyfileobj(r.raw, ird_file)
+
+
+def ird_by_game_id(game_id):
+ gameid = game_id.replace('-','')
+ r = requests.get(ALL_IRD_NET_LOC, headers = {'User-Agent': 'Anonymous (You)' }, timeout=5)
+ soup = BeautifulSoup(r.text, "html.parser")
+
+ ird_name = False
+ for elem in soup.find_all("a"):
+ url = elem.get('href').split('/')[-1].replace('\\"','')
+ if gameid in url:
+ ird_name = url
+
+ if not ird_name:
+ error("Unable to download IRD, couldn't find link")
+
+ download_ird(ird_name)
+
+ return(ird_name)
+
+
# Main functions
diff --git a/libray/ird.py b/libray/ird.py
new file mode 100644
index 0000000..e357222
--- /dev/null
+++ b/libray/ird.py
@@ -0,0 +1,137 @@
+# -*- coding: utf8 -*-
+
+# libray - Libre Blu-Ray PS3 ISO Tool
+# Copyright (C) 2018 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
+
+try:
+ from libray import core
+except ImportError:
+ import core
+
+
+class IRD:
+
+
+ ORDER = 'little'
+ TEMP_FILE = 'ird'
+ MAGIC_STRING = b"3IRD"
+
+
+ def __init__(self, args):
+
+ self.uncompress(args.ird) # TODO: Try/Except
+
+ self.size = core.filesize(self.TEMP_FILE)
+ with open(self.TEMP_FILE, '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 i 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 i 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)
+
+ unused_bytes = input_ird.read(4) # Yeah, I don't know either.
+
+ 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 args.verbose:
+ self.print_info()
+
+ os.remove(self.TEMP_FILE)
+
+
+ def get_if_exists(self, input_ird):
+ starting_address = input_ird.tell()
+ length = core.read_seven_bit_encoded_int(input_ird, self.ORDER)
+ print(length)
+ if length:
+ return input_ird.read(length)
+
+ input_ird.seek(starting_address)
+ return None
+
+
+ def uncompress(self, filename):
+ uncompress = False
+ with open(filename, 'rb') as input_ird:
+ if input_ird.read(4) != self.MAGIC_STRING:
+ uncompress = True
+
+ if uncompress:
+ with open(filename, 'rb') as gzfile:
+ with open(self.TEMP_FILE, 'wb') as tmpfile:
+ tmpfile.write(zlib.decompress(gzfile.read(), zlib.MAX_WBITS|16))
+ else:
+ shutil.copyfile(filename, self.TEMP_FILE)
+
+
+
+ def print_info(self):
+ 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)
+ print('File Count: %s' % self.file_count)
+ print('Data1: %s' % self.data1.hex())
+ print('Data2: %s' % self.data2.hex())
+
+
diff --git a/libray/iso.py b/libray/iso.py
index 6bcb01c..29f3837 100644
--- a/libray/iso.py
+++ b/libray/iso.py
@@ -25,8 +25,10 @@ from Crypto.Cipher import AES
try:
from libray import core
+ from libray import ird
except ImportError:
import core
+ import ird
class ISO:
@@ -43,10 +45,6 @@ class ISO:
self.regions = self.read_regions(input_iso, args.iso)
- input_iso.seek(3968)
- self.data1 = input_iso.read(16)
-
-
input_iso.seek(core.SECTOR)
playstation = input_iso.read(16)
self.game_id = input_iso.read(16).decode('utf8').strip()
@@ -54,11 +52,20 @@ class ISO:
if args.verbose:
self.print_info()
+ if not args.ird:
+ core.warning('No IRD file specified, downloading required file')
+ args.ird = core.ird_by_game_id(self.game_id) # Download ird
+
+ self.ird = ird.IRD(args)
+
+ if self.ird.region_count != len(self.regions)-1:
+ core.error('Corrupt ISO. Expected %s regions, found %s regions' % (self.ird.region_count, len(self.regions)-1))
+
if self.regions[-1]['start'] > self.size:
core.error('Corrupt ISO. Expected filesize larger than %.2f GiB, actual size is %.2f GiB' % (self.regions[-1]['start'] / 1024**3, self.size / 1024**3 ) )
cipher = AES.new(core.ISO_SECRET, AES.MODE_CBC, core.ISO_IV)
- self.disc_key = cipher.encrypt(self.data1)
+ self.disc_key = cipher.encrypt(self.ird.data1)
def decrypt(self, args):
diff --git a/libray/libray.py b/libray/libray.py
index 5665ae3..89cc4cb 100755
--- a/libray/libray.py
+++ b/libray/libray.py
@@ -19,7 +19,7 @@
# You should have received a copy of the GNU General Public License
# along with libray. If not, see .
-import sys
+
import argparse
try:
@@ -34,13 +34,11 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description='A Libre (FLOSS) Python application for unencrypting, extracting, repackaging, and encrypting PS3 ISOs')
parser.add_argument('-v', '--verbose', help="Increase verbosity", action='count')
parser.add_argument('-o', '--output', dest='output', type=str, help="Output filename", default='output.iso')
+ parser.add_argument('-k', '--ird', dest='ird', type=str, help="Path to .ird file", default="")
required = parser.add_argument_group('required arguments')
required.add_argument('-i', '--iso', dest='iso', type=str, help="Path to .iso file", required=True)
args = parser.parse_args()
- if args.iso:
- core.decrypt(args)
- sys.exit()
+ core.decrypt(args)
- print("Not enough arguments given. See --help")
\ No newline at end of file