Release 0.0.6

This commit is contained in:
Nichlas Severinsen 2021-06-05 22:03:31 +02:00
parent 5e9bd4f257
commit f3e8132a37
8 changed files with 300 additions and 35 deletions

View file

@ -22,6 +22,7 @@
import os
import sys
import stat
import zlib
import shutil
import requests
from bs4 import BeautifulSoup
@ -89,13 +90,22 @@ def read_seven_bit_encoded_int(fileobj, order):
def error(msg):
"""Print fatal error message and terminate"""
print('ERROR: %s' % msg)
print('[ERROR] %s' % msg)
sys.exit(1)
def warning(msg):
"""Print a warning message"""
print('WARNING: %s. Continuing regardless.' % msg)
def warning(msg, args):
"""Print a warning message. Warning messages can be silenced with --quiet"""
if not args.quiet:
print('[WARNING] %s. Continuing regardless.' % msg)
def vprint(msg, args):
"""Vprint, verbose print, can be silenced with --quiet"""
if not args.quiet:
print('[*] ' + msg)
def download_ird(ird_name):
@ -136,6 +146,22 @@ def ird_by_game_id(game_id):
return(ird_name)
def crc32(filename):
"""Calculate crc32 for file"""
with open(filename, 'rb') as infile:
crc32 = 0
while True:
data = infile.read(65536)
if not data:
break
crc32 = zlib.crc32(data, crc32)
return "%08X" % (crc32 & 0xFFFFFFFF)
# Main functions

View file

@ -20,6 +20,8 @@
import sys
import sqlite3
import pkg_resources
from tqdm import tqdm
from Crypto.Cipher import AES
@ -96,35 +98,67 @@ class ISO:
self.game_id = input_iso.read(16).decode('utf8').strip()
if args.verbose and not args.quiet:
self.print_info()
cipher = AES.new(core.ISO_SECRET, AES.MODE_CBC, core.ISO_IV)
if not args.decryption_key:
if not args.ird:
if not args.quiet:
core.warning('No IRD file specified, finding required file')
args.ird = core.ird_by_game_id(self.game_id) # Download ird
# No key or .ird specified. Let's first check if keys.db is packaged with this release
self.ird = ird.IRD(args)
redump = False
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))
core.vprint('Checking for bundled redump keys', args)
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 ) )
try:
db = sqlite3.connect(pkg_resources.resource_filename(__name__, 'data/keys.db'))
c = db.cursor()
self.disc_key = cipher.encrypt(self.ird.data1)
core.vprint('Calculating crc32', args)
crc32 = core.crc32(args.iso)
keys = c.execute('SELECT * FROM games WHERE crc32=?', [crc32.lower()]).fetchall()
if keys:
self.disc_key = keys[0][-1]
core.vprint('.ISO identified as "%s"' % keys[0][0], args)
redump = True
else:
raise ValueError
except:
core.vprint('No keys found', args)
if not redump:
# Fallback to checking if an .ird exists
core.warning('No IRD file specified, finding required file', args)
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 or error in IRD. 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 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 ) )
self.disc_key = cipher.encrypt(self.ird.data1)
else:
self.disc_key = core.to_bytes(args.decryption_key)
if args.verbose and not args.quiet:
self.print_info()
def decrypt(self, args):
"""Decrypt self using args from argparse."""
if not args.quiet:
print('Decrypting with disc key: %s' % self.disc_key.hex())
core.vprint('Decrypting with disc key: %s' % self.disc_key.hex(), args)
with open(args.iso, 'rb') as input_iso:
@ -145,8 +179,8 @@ class ISO:
if not region['enc']:
while input_iso.tell() < region['end']:
data = input_iso.read(core.SECTOR)
if not data and not args.quiet:
core.warning('Trying to read past the end of the file')
if not data:
core.warning('Trying to read past the end of the file', args)
break
output_iso.write(data)
@ -163,8 +197,8 @@ class ISO:
num >>= 8
data = input_iso.read(core.SECTOR)
if not data and not args.quiet:
core.warning('Trying to read past the end of the file')
if not data:
core.warning('Trying to read past the end of the file', args)
break
cipher = AES.new(self.disc_key, AES.MODE_CBC, bytes(iv))
@ -177,14 +211,14 @@ class ISO:
if not args.quiet:
pbar.close()
print('Decryption complete.')
core.vprint('Decryption complete!', args)
def encrypt(self, args):
"""Encrypt self using args from argparse."""
if not args.quiet:
print('Re-encrypting with disc key: %s' % self.disc_key.hex())
core.vprint('Re-encrypting with disc key: %s' % self.disc_key.hex(), args)
with open(args.iso, 'rb') as input_iso:
@ -205,8 +239,8 @@ class ISO:
if not region['enc']:
while input_iso.tell() < region['end']:
data = input_iso.read(core.SECTOR)
if not data and not args.quiet:
core.warning('Trying to read past the end of the file')
if not data:
core.warning('Trying to read past the end of the file', args)
break
output_iso.write(data)
@ -223,8 +257,8 @@ class ISO:
num >>= 8
data = input_iso.read(core.SECTOR)
if not data and not args.quiet:
core.warning('Trying to read past the end of the file')
if not data:
core.warning('Trying to read past the end of the file', args)
break
cipher = AES.new(self.disc_key, AES.MODE_CBC, bytes(iv))
@ -235,6 +269,11 @@ class ISO:
if not args.quiet:
pbar.update(1)
if not args.quiet:
pbar.close()
core.vprint('Re-encryption complete!', args)
def print_info(self):
# TODO: This could probably have been a __str__? Who cares?