core.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. import os
  20. import sys
  21. import stat
  22. import zlib
  23. import shutil
  24. import requests
  25. from bs4 import BeautifulSoup
  26. try:
  27. from libray import iso
  28. from libray import ird
  29. except ImportError:
  30. import iso
  31. import ird
  32. # Magic numbers / Constant variables
  33. SECTOR = 2048
  34. ALL_IRD_NET_LOC = 'http://jonnysp.bplaced.net/data.php'
  35. GET_IRD_NET_LOC = 'http://jonnysp.bplaced.net/ird/'
  36. # Utility functions
  37. def to_int(data, byteorder='big'):
  38. """Convert bytes to integer"""
  39. if isinstance(data, bytes):
  40. return int.from_bytes(data, byteorder)
  41. def to_bytes(data):
  42. """Convert a string of HEX to bytes"""
  43. if isinstance(data, str):
  44. return bytes(bytearray.fromhex(data))
  45. ISO_SECRET = to_bytes("380bcf0b53455b3c7817ab4fa3ba90ed")
  46. ISO_IV = to_bytes("69474772af6fdab342743aefaa186287")
  47. def size(path):
  48. """Get size of a file or block device in bytes"""
  49. pathstat = os.stat(path)
  50. # Check if it's a block device
  51. if stat.S_ISBLK(pathstat.st_mode):
  52. return open(path, 'rb').seek(0, os.SEEK_END)
  53. # Otherwise, it's hopefully a file
  54. return pathstat.st_size
  55. def read_seven_bit_encoded_int(fileobj, order):
  56. """Read an Int32, 7 bits at a time."""
  57. # The highest bit of the byte, when on, means to continue reading more bytes.
  58. count = 0
  59. shift = 0
  60. byte = -1
  61. while (byte & 0x80) != 0 or byte == -1:
  62. # Check for a corrupted stream. Read a max of 5 bytes.
  63. if shift == (5 * 7):
  64. raise ValueError
  65. byte = to_int(fileobj.read(1), order)
  66. count |= (byte & 0x7F) << shift
  67. shift += 7
  68. return count
  69. def error(msg):
  70. """Print fatal error message and terminate"""
  71. print('[ERROR] %s' % msg, file=sys.stderr)
  72. sys.exit(1)
  73. def warning(msg, args):
  74. """Print a warning message. Warning messages can be silenced with --quiet"""
  75. if not args.quiet:
  76. print('[WARNING] %s. Continuing regardless.' % msg, file=sys.stderr)
  77. def vprint(msg, args):
  78. """Vprint, verbose print, can be silenced with --quiet"""
  79. if not args.quiet:
  80. print('[*] ' + msg)
  81. def download_ird(ird_name):
  82. """Download an .ird from GET_IRD_NET_LOC"""
  83. # Check if file already exists and skip if it does
  84. if os.path.exists(ird_name): # TODO: might want to check that the file is valid first, could do a HEAD agains the url
  85. return
  86. ird_link = GET_IRD_NET_LOC + ird_name
  87. r = requests.get(ird_link, stream=True)
  88. with open(ird_name, 'wb') as ird_file:
  89. r.raw.decode_content = True
  90. shutil.copyfileobj(r.raw, ird_file)
  91. def ird_by_game_id(game_id):
  92. """Using a game_id, download the responding .ird from ALL_IRD_NET_LOC"""
  93. gameid = game_id.replace('-', '')
  94. try:
  95. r = requests.get(ALL_IRD_NET_LOC, headers={'User-Agent': 'Anonymous (You)'}, timeout=5)
  96. except requests.exceptions.ReadTimeout:
  97. error('Server timed out, fix your connection or manually specify a key/ird.')
  98. soup = BeautifulSoup(r.text, "html.parser")
  99. ird_name = False
  100. for elem in soup.find_all("a"):
  101. url = elem.get('href').split('/')[-1].replace('\\"', '')
  102. if gameid in url:
  103. ird_name = url
  104. if not ird_name:
  105. error("Unable to download IRD, couldn't find link. You could specify the decryption key with -d if you have it.")
  106. download_ird(ird_name)
  107. return (ird_name)
  108. def crc32(filename, keep_going=[True]):
  109. """Calculate crc32 for file"""
  110. with open(filename, 'rb') as infile:
  111. crc32 = 0
  112. while keep_going[0] == True:
  113. data = infile.read(65536)
  114. if not data:
  115. break
  116. crc32 = zlib.crc32(data, crc32)
  117. if keep_going[0] == False:
  118. return None
  119. return "%08X" % (crc32 & 0xFFFFFFFF)
  120. def serial_country(title):
  121. """Get country from disc serial / productcode / title_id"""
  122. if title[2] == 'A':
  123. return 'Asia'
  124. if title[2] == 'C':
  125. return 'China'
  126. if title[2] == 'E':
  127. return 'Europe'
  128. if title[2] == 'H':
  129. return 'Hong Kong'
  130. if title[2] == 'J' or title[2] == 'P':
  131. return 'Japan'
  132. if title[2] == 'K':
  133. return 'Korea'
  134. if title[2] == 'U' or title[2] == 'T':
  135. return 'USA'
  136. raise ValueError('Unknown country?!')
  137. def multiman_title(title):
  138. """Fix special characters in title for Multiman style"""
  139. replace = {
  140. ':': ' -',
  141. '/': '-',
  142. '™': '',
  143. '®': '',
  144. }
  145. for key, val in replace.items():
  146. title = title.replace(key, val)
  147. return title
  148. # Main functions
  149. def info(args):
  150. """Print information about .iso and then quit."""
  151. if args.iso:
  152. input_iso = iso.ISO(args)
  153. input_iso.print_info()
  154. sys.exit()
  155. if args.ird:
  156. input_ird = ird.IRD(args.ird)
  157. input_ird.print_info(regions=True)
  158. sys.exit()
  159. def decrypt(args):
  160. """Try to decrypt a given .iso using relevant .ird or encryption key from argparse
  161. If no .ird is given this will try to automatically download an .ird file with the encryption/decryption key for the given game .iso
  162. """
  163. input_iso = iso.ISO(args)
  164. # TODO: some of the logic should probably be moved up here instead of residing in the decrypt function
  165. input_iso.decrypt(args)
  166. def encrypt(args):
  167. """Try to re-encrypt a decrypted .iso using relevant .ird or encryption key from argparse
  168. If no .ird is given this will try to automatically download an .ird file with the encryption/decryption key for the given game .iso
  169. """
  170. input_iso = iso.ISO(args)
  171. input_iso.encrypt(args)