-
Notifications
You must be signed in to change notification settings - Fork 0
/
rdr2_enc_dec.py
105 lines (77 loc) · 3.97 KB
/
rdr2_enc_dec.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
from io import BytesIO
import struct
import argparse
import re
from Crypto.Cipher import AES
GTA5_AND_REDDEAD_KEY = AES.new(b'\x16\x85\xff\xa3\x8d\x01\x0f\r\xfef\x1c\xf9\xb5W,P\r\x80&H\xdb7\xb9\xed\x0fH\xc5sB\xc0"\xf5', AES.MODE_ECB)
def rdr2_checksum(data: bytes, /) -> int:
checksum = 0x3fac7125
for char in data:
char = (char + 128) % 256 - 128 # casting to signed char
checksum = ((char + checksum) * 0x401) & 0xFFFFFFFF
checksum = (checksum >> 6 ^ checksum) & 0xFFFFFFFF
checksum = (checksum*9) & 0xFFFFFFFF
return ((checksum >> 11 ^ checksum) * 0x8001) & 0xFFFFFFFF
def _crypt_rdr2_ps4_save(rdr2_save: BytesIO,/,*,enc_data_offset: int,do_enc: bool = True) -> bytes:
previous_spot = rdr2_save.tell()
rdr2_save.seek(0)
rdr2_save.seek(enc_data_offset)
crypted_data = rdr2_save.read()
rdr2_save.seek(0)
if do_enc:
for chunk in [m.start() for m in re.finditer(b'CHKS\x00', crypted_data)]: # calculate checksums for each chunk
rdr2_save.seek(enc_data_offset + chunk + 4,0) # 4 bytes for the magic CHKS
header_size = struct.unpack('>I',rdr2_save.read(4))[0]
data_length = struct.unpack('>I',rdr2_save.read(4))[0]
rdr2_save.seek(header_size - 4 - 4 - 4,1) # 4 for the header size num, 4 for the data length num and 4 for the checksum
rdr2_save.seek(-data_length,1)
data_to_be_hashed = bytearray(rdr2_save.read(data_length))
chks_offset = len(data_to_be_hashed)-header_size + (4 + 4)
data_to_be_hashed[chks_offset:chks_offset + (4 + 4)] = b'\x00' * (4 + 4) # remove the length and hash
new_hash = struct.pack('>I',rdr2_checksum(data_to_be_hashed))
rdr2_save.seek(enc_data_offset + chunk + (4 + 4 + 4),0) # 4 bytes for header size num, 4 bytes for the data length and 4 bytes for the checksum
rdr2_save.write(new_hash)
rdr2_save.seek(0)
rdr2_save.seek(enc_data_offset)
crypted_data = rdr2_save.read()
rdr2_save.seek(0)
if do_enc:
dec_data = GTA5_AND_REDDEAD_KEY.encrypt(crypted_data)
else:
dec_data = GTA5_AND_REDDEAD_KEY.decrypt(crypted_data)
result = rdr2_save.read(enc_data_offset) + dec_data
rdr2_save.seek(previous_spot)
return result
def encrypt_rdr2_ps4_save(rdr2_save: BytesIO,/,*,enc_data_offset: int) -> bytes:
return _crypt_rdr2_ps4_save(rdr2_save,enc_data_offset=enc_data_offset,do_enc=True)
def decrypt_rdr2_ps4_save(rdr2_save: BytesIO,/,*,enc_data_offset: int) -> bytes:
return _crypt_rdr2_ps4_save(rdr2_save,enc_data_offset=enc_data_offset,do_enc=False)
def auto_encrypt_decrypt(rdr2_save: BytesIO,/) -> bytes:
previous_spot = rdr2_save.tell()
rdr2_save.seek(0)
rdr2_save.seek(0x114)
if rdr2_save.read(4) == b'\x00\x00\x00\x00':
ENC_DATA_OFFSET = 0x120
DEC_SAVE_HEADER = b'RSAV'
else:
ENC_DATA_OFFSET = 0x114
DEC_SAVE_HEADER = b'PSIN'
rdr2_save.seek(0)
rdr2_save.seek(ENC_DATA_OFFSET)
header = rdr2_save.read(4)
rdr2_save.seek(previous_spot)
if header == DEC_SAVE_HEADER:
return encrypt_rdr2_ps4_save(rdr2_save,enc_data_offset=ENC_DATA_OFFSET)
else:
return decrypt_rdr2_ps4_save(rdr2_save,enc_data_offset=ENC_DATA_OFFSET)
def main():
parser = argparse.ArgumentParser(description='Simple tool to encrypt and decrypt ps4 saves for Red Dead Redemption 2 and GTAV, detects if its encrypted or decrypted automatically')
parser.add_argument("input_save", help="Path to the save file")
parser.add_argument("output_save", help="Path to write the output save")
args = parser.parse_args()
with open(args.input_save,'rb') as f:
old_data = BytesIO(f.read())
with open(args.output_save,'wb') as f:
f.write(auto_encrypt_decrypt(old_data))
if __name__ == '__main__':
main()