A tool for deriving PKG packet encryption keys for ps4 written in c++
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | RIF File Analyzer and Generator |
| 4 | Analyzes PlayStation 4 RIF (Rights Information File) structure |
| 5 | and generates dynamic RIF files for retail games |
| 6 | """ |
| 7 | |
| 8 | import os |
| 9 | import struct |
| 10 | import hashlib |
| 11 | from typing import Dict, List, Tuple |
| 12 | from pathlib import Path |
| 13 | |
| 14 | class RIFAnalyzer: |
| 15 | def __init__(self): |
| 16 | self.rif_structure = { |
| 17 | 'magic': b'RIF\x00', # 4 bytes: "RIF" + null terminator |
| 18 | 'version': b'\x00\x01', # 2 bytes: version (0x0001) |
| 19 | 'unknown1': b'\xFF\xFF', # 2 bytes: unknown field |
| 20 | 'padding1': b'\x00' * 12, # 12 bytes: padding/reserved |
| 21 | 'timestamp_offset': 20, # Offset where timestamp starts |
| 22 | 'padding2_pattern': b'\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF' # 8 bytes pattern |
| 23 | } |
| 24 | |
| 25 | def analyze_rif_file(self, filepath: str) -> Dict: |
| 26 | """Analyze a RIF file and extract its structure""" |
| 27 | with open(filepath, 'rb') as f: |
| 28 | data = f.read() |
| 29 | |
| 30 | if len(data) != 1024: |
| 31 | raise ValueError(f"Invalid RIF file size: {len(data)} bytes (expected 1024)") |
| 32 | |
| 33 | analysis = { |
| 34 | 'filepath': filepath, |
| 35 | 'size': len(data), |
| 36 | 'magic': data[0:4], |
| 37 | 'version': data[4:6], |
| 38 | 'unknown1': data[6:8], |
| 39 | 'padding1': data[8:20], |
| 40 | 'timestamp': struct.unpack('>I', data[20:24])[0], # Big-endian 32-bit timestamp |
| 41 | 'padding2': data[24:32], |
| 42 | 'content_id': self.extract_content_id(filepath), |
| 43 | 'header_hex': data[0:32].hex().upper() |
| 44 | } |
| 45 | |
| 46 | return analysis |
| 47 | |
| 48 | def extract_content_id(self, filepath: str) -> str: |
| 49 | """Extract content ID from filename""" |
| 50 | filename = os.path.basename(filepath) |
| 51 | if '_' in filename: |
| 52 | return filename.split('_')[0] + '_' + filename.split('_')[1] |
| 53 | return filename.replace('.rif', '') |
| 54 | |
| 55 | def analyze_directory(self, directory: str) -> List[Dict]: |
| 56 | """Analyze all RIF files in a directory""" |
| 57 | results = [] |
| 58 | rif_files = Path(directory).rglob('*.rif') |
| 59 | |
| 60 | for rif_file in rif_files: |
| 61 | try: |
| 62 | analysis = self.analyze_rif_file(str(rif_file)) |
| 63 | results.append(analysis) |
| 64 | print(f"Analyzed: {rif_file.name}") |
| 65 | except Exception as e: |
| 66 | print(f"Error analyzing {rif_file}: {e}") |
| 67 | |
| 68 | return results |
| 69 | |
| 70 | def find_patterns(self, analyses: List[Dict]) -> Dict: |
| 71 | """Find common patterns across RIF files""" |
| 72 | patterns = { |
| 73 | 'common_magic': set(), |
| 74 | 'common_version': set(), |
| 75 | 'common_unknown1': set(), |
| 76 | 'timestamp_range': [], |
| 77 | 'common_padding2': set() |
| 78 | } |
| 79 | |
| 80 | for analysis in analyses: |
| 81 | patterns['common_magic'].add(analysis['magic']) |
| 82 | patterns['common_version'].add(analysis['version']) |
| 83 | patterns['common_unknown1'].add(analysis['unknown1']) |
| 84 | patterns['timestamp_range'].append(analysis['timestamp']) |
| 85 | patterns['common_padding2'].add(analysis['padding2']) |
| 86 | |
| 87 | patterns['timestamp_range'] = (min(patterns['timestamp_range']), max(patterns['timestamp_range'])) |
| 88 | |
| 89 | return patterns |
| 90 | |
| 91 | class RIFGenerator: |
| 92 | def __init__(self): |
| 93 | self.base_structure = { |
| 94 | 'magic': b'RIF\x00', |
| 95 | 'version': b'\x00\x01', |
| 96 | 'unknown1': b'\xFF\xFF', |
| 97 | 'padding1': b'\x00' * 12, |
| 98 | 'padding2': b'\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF' |
| 99 | } |
| 100 | |
| 101 | def generate_timestamp(self, content_id: str) -> int: |
| 102 | """Generate a deterministic timestamp based on content ID""" |
| 103 | # Create a hash of the content ID and use it to generate a timestamp |
| 104 | hash_obj = hashlib.md5(content_id.encode()) |
| 105 | hash_int = int(hash_obj.hexdigest()[:8], 16) |
| 106 | |
| 107 | # Map to a reasonable timestamp range (2013-2024) |
| 108 | base_timestamp = 0x52000000 # Around 2013 |
| 109 | max_offset = 0x10000000 # About 11 years range |
| 110 | |
| 111 | timestamp = base_timestamp + (hash_int % max_offset) |
| 112 | return timestamp |
| 113 | |
| 114 | def generate_rif_content(self, content_id: str) -> bytes: |
| 115 | """Generate RIF file content for a given content ID""" |
| 116 | # Start with base structure |
| 117 | rif_data = bytearray(1024) # Initialize 1024-byte array with zeros |
| 118 | |
| 119 | # Fill in the known structure |
| 120 | offset = 0 |
| 121 | |
| 122 | # Magic number |
| 123 | rif_data[offset:offset+4] = self.base_structure['magic'] |
| 124 | offset += 4 |
| 125 | |
| 126 | # Version |
| 127 | rif_data[offset:offset+2] = self.base_structure['version'] |
| 128 | offset += 2 |
| 129 | |
| 130 | # Unknown field |
| 131 | rif_data[offset:offset+2] = self.base_structure['unknown1'] |
| 132 | offset += 2 |
| 133 | |
| 134 | # Padding 1 |
| 135 | rif_data[offset:offset+12] = self.base_structure['padding1'] |
| 136 | offset += 12 |
| 137 | |
| 138 | # Timestamp (big-endian) |
| 139 | timestamp = self.generate_timestamp(content_id) |
| 140 | rif_data[offset:offset+4] = struct.pack('>I', timestamp) |
| 141 | offset += 4 |
| 142 | |
| 143 | # Padding 2 |
| 144 | rif_data[offset:offset+8] = self.base_structure['padding2'] |
| 145 | offset += 8 |
| 146 | |
| 147 | # Fill remaining bytes with pattern or zeros |
| 148 | # The rest of the file appears to be mostly zeros or encrypted data |
| 149 | # For a basic generator, we'll leave it as zeros |
| 150 | |
| 151 | return bytes(rif_data) |
| 152 | |
| 153 | def generate_rif_file(self, content_id: str, output_path: str) -> bool: |
| 154 | """Generate a RIF file for the given content ID""" |
| 155 | try: |
| 156 | rif_content = self.generate_rif_content(content_id) |
| 157 | |
| 158 | with open(output_path, 'wb') as f: |
| 159 | f.write(rif_content) |
| 160 | |
| 161 | print(f"Generated RIF file: {output_path}") |
| 162 | return True |
| 163 | |
| 164 | except Exception as e: |
| 165 | print(f"Error generating RIF file: {e}") |
| 166 | return False |
| 167 | |
| 168 | def main(): |
| 169 | # Analyze existing RIF files |
| 170 | analyzer = RIFAnalyzer() |
| 171 | rif_directory = r"c:\Users\marco\source\repos\TEST-SC\ShadPKG\gift\RIF Files" |
| 172 | |
| 173 | print("Analyzing existing RIF files...") |
| 174 | analyses = analyzer.analyze_directory(rif_directory) |
| 175 | |
| 176 | print(f"\nAnalyzed {len(analyses)} RIF files") |
| 177 | |
| 178 | # Find patterns |
| 179 | patterns = analyzer.find_patterns(analyses) |
| 180 | print("\nCommon patterns found:") |
| 181 | print(f"Magic: {patterns['common_magic']}") |
| 182 | print(f"Version: {patterns['common_version']}") |
| 183 | print(f"Unknown1: {patterns['common_unknown1']}") |
| 184 | print(f"Timestamp range: {hex(patterns['timestamp_range'][0])} - {hex(patterns['timestamp_range'][1])}") |
| 185 | |
| 186 | # Example: Generate a new RIF file |
| 187 | generator = RIFGenerator() |
| 188 | |
| 189 | # Test with a retail game content ID |
| 190 | test_content_id = "EP0001-CUSA12345_00-TESTGAMERETAIL01" |
| 191 | output_file = f"{test_content_id}.rif" |
| 192 | |
| 193 | print(f"\nGenerating test RIF file for: {test_content_id}") |
| 194 | success = generator.generate_rif_file(test_content_id, output_file) |
| 195 | |
| 196 | if success: |
| 197 | print(f"Test RIF file generated successfully: {output_file}") |
| 198 | |
| 199 | # Analyze the generated file to verify structure |
| 200 | test_analysis = analyzer.analyze_rif_file(output_file) |
| 201 | print(f"Generated file analysis:") |
| 202 | print(f" Size: {test_analysis['size']} bytes") |
| 203 | print(f" Magic: {test_analysis['magic']}") |
| 204 | print(f" Version: {test_analysis['version']}") |
| 205 | print(f" Timestamp: {hex(test_analysis['timestamp'])}") |
| 206 | print(f" Header: {test_analysis['header_hex']}") |
| 207 | |
| 208 | if __name__ == "__main__": |
| 209 | main() |