Seregon/ShadPKG

A tool for deriving PKG packet encryption keys for ps4 written in c++

C++/47.3 KB/No license
scripts/rif_generator_cli.py
ShadPKG / scripts / rif_generator_cli.py
1#!/usr/bin/env python3
2"""
3RIF Generator CLI
4Command-line interface for generating PlayStation 4 RIF files for retail games
5 
6Usage:
7 python rif_generator_cli.py <content_id> [output_directory]
8 python rif_generator_cli.py --batch <file_with_content_ids> [output_directory]
9 python rif_generator_cli.py --gui # Launch GUI version
10 
11Examples:
12 python rif_generator_cli.py EP0001-CUSA12345_00-TESTGAMERETAIL01
13 python rif_generator_cli.py UP0001-CUSA67890_00-ANOTHERGAME12345 ./output
14 python rif_generator_cli.py --batch content_ids.txt ./rif_files
15"""
16 
17import sys
18import os
19import argparse
20import struct
21import hashlib
22from datetime import datetime
23from pathlib import Path
24 
25class RIFGenerator:
26 def __init__(self):
27 self.base_structure = {
28 'magic': b'RIF\x00',
29 'version': b'\x00\x01',
30 'unknown1': b'\xFF\xFF',
31 'padding1': b'\x00' * 12,
32 'padding2': b'\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
33 }
34
35 def validate_content_id(self, content_id: str) -> bool:
36 """Validate PlayStation 4 Content ID format"""
37 if not content_id:
38 return False
39
40 # Basic validation - Content ID format: REGION-CUSAXXXXX_XX-PRODUCTCODE
41 parts = content_id.split('-')
42 if len(parts) != 3:
43 print(f"Error: Invalid Content ID format. Expected: REGION-CUSAXXXXX_XX-PRODUCTCODE")
44 print(f"Example: EP0001-CUSA12345_00-TESTGAMERETAIL01")
45 return False
46
47 region_part = parts[0]
48 cusa_version_part = parts[1]
49 product_part = parts[2]
50
51 # Validate region (first part)
52 if not region_part.startswith(('EP', 'UP', 'JP')):
53 print(f"Error: Invalid region '{region_part}'. Must start with EP, UP, or JP")
54 return False
55
56 # Validate CUSA and version part
57 if '_' not in cusa_version_part:
58 print(f"Error: Invalid format. Missing version separator in '{cusa_version_part}'")
59 return False
60
61 cusa_part, version_part = cusa_version_part.split('_', 1)
62
63 if not cusa_part.startswith('CUSA') or len(cusa_part) != 9:
64 print(f"Error: Invalid CUSA format '{cusa_part}'. Must be CUSAXXXXX (5 digits)")
65 return False
66
67 if len(version_part) != 2 or not version_part.isdigit():
68 print(f"Error: Invalid version format '{version_part}'. Must be 2 digits")
69 return False
70
71 # Validate product code
72 if len(product_part) != 16:
73 print(f"Error: Invalid product code '{product_part}'. Must be 16 characters")
74 return False
75
76 return True
77
78 def generate_timestamp(self, content_id: str) -> int:
79 """Generate a deterministic timestamp based on content ID"""
80 hash_obj = hashlib.md5(content_id.encode())
81 hash_int = int(hash_obj.hexdigest()[:8], 16)
82
83 # Map to a reasonable timestamp range (2013-2024)
84 base_timestamp = 0x52000000 # Around 2013
85 max_offset = 0x10000000 # About 11 years range
86
87 timestamp = base_timestamp + (hash_int % max_offset)
88 return timestamp
89
90 def generate_rif_content(self, content_id: str) -> bytes:
91 """Generate RIF file content for a given content ID"""
92 rif_data = bytearray(1024) # Initialize 1024-byte array with zeros
93
94 offset = 0
95
96 # Magic number: "RIF\0"
97 rif_data[offset:offset+4] = self.base_structure['magic']
98 offset += 4
99
100 # Version: 0x0001
101 rif_data[offset:offset+2] = self.base_structure['version']
102 offset += 2
103
104 # Unknown field: 0xFFFF
105 rif_data[offset:offset+2] = self.base_structure['unknown1']
106 offset += 2
107
108 # Padding 1: 12 bytes of zeros
109 rif_data[offset:offset+12] = self.base_structure['padding1']
110 offset += 12
111
112 # Timestamp (big-endian)
113 timestamp = self.generate_timestamp(content_id)
114 rif_data[offset:offset+4] = struct.pack('>I', timestamp)
115 offset += 4
116
117 # Padding 2: specific pattern
118 rif_data[offset:offset+8] = self.base_structure['padding2']
119 offset += 8
120
121 return bytes(rif_data)
122
123 def generate_rif_file(self, content_id: str, output_path: str = None) -> bool:
124 """Generate a RIF file for the given content ID"""
125 if not self.validate_content_id(content_id):
126 return False
127
128 try:
129 rif_content = self.generate_rif_content(content_id)
130
131 if output_path is None:
132 output_path = f"{content_id}.rif"
133 elif os.path.isdir(output_path):
134 output_path = os.path.join(output_path, f"{content_id}.rif")
135
136 # Create output directory if it doesn't exist
137 os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
138
139 with open(output_path, 'wb') as f:
140 f.write(rif_content)
141
142 timestamp = self.generate_timestamp(content_id)
143 timestamp_date = datetime.fromtimestamp(timestamp)
144
145 print(f"✓ Generated RIF file: {output_path}")
146 print(f" Content ID: {content_id}")
147 print(f" Size: {len(rif_content)} bytes")
148 print(f" Timestamp: {hex(timestamp)} ({timestamp_date.strftime('%Y-%m-%d %H:%M:%S')})")
149
150 return True
151
152 except Exception as e:
153 print(f"✗ Error generating RIF file for {content_id}: {e}")
154 return False
155
156 def generate_batch(self, content_ids_file: str, output_dir: str = ".") -> int:
157 """Generate RIF files for multiple content IDs from a file"""
158 try:
159 with open(content_ids_file, 'r') as f:
160 content_ids = [line.strip() for line in f if line.strip() and not line.startswith('#')]
161
162 if not content_ids:
163 print(f"No valid content IDs found in {content_ids_file}")
164 return 0
165
166 print(f"Processing {len(content_ids)} content IDs from {content_ids_file}")
167 print(f"Output directory: {os.path.abspath(output_dir)}")
168 print("-" * 60)
169
170 success_count = 0
171 for i, content_id in enumerate(content_ids, 1):
172 print(f"[{i}/{len(content_ids)}] Processing: {content_id}")
173 if self.generate_rif_file(content_id, output_dir):
174 success_count += 1
175 print()
176
177 print("-" * 60)
178 print(f"Batch processing complete: {success_count}/{len(content_ids)} files generated successfully")
179
180 return success_count
181
182 except FileNotFoundError:
183 print(f"Error: File '{content_ids_file}' not found")
184 return 0
185 except Exception as e:
186 print(f"Error processing batch file: {e}")
187 return 0
188 
189def print_examples():
190 """Print usage examples"""
191 examples = """
192Content ID Examples:
193 EP0001-CUSA00074_00-CHILDOFLIGHT0001 (Child of Light - Europe)
194 UP0001-CUSA07346_00-EAGLEFLIGHTDEMO1 (Eagle Flight Demo - US)
195 JP0001-CUSA12345_00-TESTGAMERETAIL01 (Test Game - Japan)
196 
197Usage Examples:
198 # Generate single RIF file in current directory
199 python rif_generator_cli.py EP0001-CUSA12345_00-TESTGAMERETAIL01
200
201 # Generate single RIF file in specific directory
202 python rif_generator_cli.py UP0001-CUSA67890_00-ANOTHERGAME12345 ./output
203
204 # Generate multiple RIF files from a text file
205 python rif_generator_cli.py --batch content_ids.txt ./rif_files
206
207 # Launch GUI version
208 python rif_generator_cli.py --gui
209 
210Batch File Format (content_ids.txt):
211 # Lines starting with # are comments
212 EP0001-CUSA12345_00-TESTGAMERETAIL01
213 UP0001-CUSA67890_00-ANOTHERGAME12345
214 JP0001-CUSA11111_00-JAPANGAMETEST001
215"""
216 print(examples)
217 
218def main():
219 parser = argparse.ArgumentParser(
220 description="PlayStation 4 RIF File Generator",
221 formatter_class=argparse.RawDescriptionHelpFormatter,
222 epilog="For more examples, use: python rif_generator_cli.py --examples"
223 )
224
225 parser.add_argument('content_id', nargs='?', help='PlayStation 4 Content ID')
226 parser.add_argument('output_dir', nargs='?', default='.', help='Output directory (default: current directory)')
227 parser.add_argument('--batch', metavar='FILE', help='Generate RIF files for content IDs listed in a file')
228 parser.add_argument('--gui', action='store_true', help='Launch GUI version')
229 parser.add_argument('--examples', action='store_true', help='Show usage examples')
230 parser.add_argument('--validate', metavar='CONTENT_ID', help='Validate a Content ID without generating file')
231
232 args = parser.parse_args()
233
234 if args.examples:
235 print_examples()
236 return 0
237
238 if args.gui:
239 try:
240 import subprocess
241 subprocess.run([sys.executable, 'rif_generator_gui.py'])
242 except Exception as e:
243 print(f"Error launching GUI: {e}")
244 print("Make sure rif_generator_gui.py is in the same directory")
245 return 0
246
247 generator = RIFGenerator()
248
249 if args.validate:
250 if generator.validate_content_id(args.validate):
251 print(f"✓ Content ID '{args.validate}' is valid")
252 return 0
253 else:
254 return 1
255
256 if args.batch:
257 success_count = generator.generate_batch(args.batch, args.output_dir)
258 return 0 if success_count > 0 else 1
259
260 if not args.content_id:
261 parser.print_help()
262 print("\nError: Content ID is required")
263 print("Use --examples to see usage examples")
264 return 1
265
266 success = generator.generate_rif_file(args.content_id, args.output_dir)
267 return 0 if success else 1
268 
269if __name__ == "__main__":
270 sys.exit(main())