From d75244f069b3aa4825bfb1f5035b2e930be52e3e Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 4 Feb 2024 00:05:56 +0100 Subject: [PATCH 1/2] Added Persona 3 Reload support --- games.json | 6 ++++++ main.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/games.json b/games.json index 3164ca5..c3f087d 100644 --- a/games.json +++ b/games.json @@ -207,6 +207,12 @@ "name": "Railway Empire 2", "package": "KalypsoMediaGroup.RailwayEmpire2Win_e60j8nnj33ga6", "handler": "railway-empire-2" + }, + // Persona 3 Reload + { + "name": "Persona 3 Reload", + "package": "SEGAofAmericaInc.L0cb6b3aea_s751p9cej88mt", + "handler": "persona-3-reload" } ] } diff --git a/main.py b/main.py index 9bf9961..55a5d6a 100644 --- a/main.py +++ b/main.py @@ -450,6 +450,43 @@ def get_save_paths( continue save_meta.append((container["name"], file["path"])) + elif handler_name == "persona-3-reload": + # "1 container, 1 file" (1c1f). Each container contains only one file which name will be the name of the container. + # The Store version of Persona 3 Reload use unencrypted savefiles, whereas the Steam version use encrypted savefiles. + # illusion0001 uncovered the encryption method and key used in this solution. + temp_save_meta = [] # Temporary list + + for container in containers: + fname = container["name"] + fname += ".sav" + fpath = container["files"][0]["path"] + temp_save_meta.append((fname, fpath)) # save_meta replaced by temp_save_meta + + # Creating a temporary folder for P3R + temp_folder = Path(temp_dir.name) / "P3R" + temp_folder.mkdir() + + key = "ae5zeitaix1joowooNgie3fahP5Ohph" #Key used to encrypt/decrypt the savefile + key_length = len(key) + + # Encryption method + for file_name, file_path in temp_save_meta: + encrypted_file_path = temp_folder / file_name + + with open(file_path, 'rb') as f: + data = f.read() + + output_data = bytearray() + for j, byte in enumerate(data): + key_byte = ord(key[j % key_length]) + encrypted_byte = ((((byte & 0xff) >> 4) & 3 | (byte & 3) << 4 | byte & 0xcc) ^ key_byte) + output_data.append(encrypted_byte) + + with open(encrypted_file_path, 'wb') as f: + f.write(bytearray(output_data)) + + save_meta.append((file_name, encrypted_file_path)) + else: raise Exception('Unsupported XGP app "%s"' % store_pkg_name) From 8b241e5a0da2cc46d786c487f60727782c36e6ee Mon Sep 17 00:00:00 2001 From: Ugo Virolle Date: Fri, 9 Feb 2024 20:03:47 +0100 Subject: [PATCH 2/2] README updated and fix typo --- README.md | 2 +- main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1bdada..0a3c023 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Legend: ✅ Confirmed working, ❔ Unconfirmed, - Not available in the store | Ninja Gaiden Sigma | ✅ | - | | Octopath Traveller | ❔ | ❔ | | Palworld | ✅ | - | +| Persona 3 Reload | ✅ | - | | Persona 5 Royal | ✅ | - | | Persona 5 Tactica | ✅ | - | | Railway Empire 2 | ❔ | ❔ | @@ -58,7 +59,6 @@ These games use different save formats than the Steam/Epic version that can't be | Chivarly 2 | [#39](https://github.com/Z1ni/XGP-save-extractor/issues/39) | | Death's Door | [#79](https://github.com/Z1ni/XGP-save-extractor/issues/79) | | Forza Horizon 4 | [#71](https://github.com/Z1ni/XGP-save-extractor/issues/71) | -| Persona 3 Reload | [#114](https://github.com/Z1ni/XGP-save-extractor/issues/114) | | Tinykin | [#28](https://github.com/Z1ni/XGP-save-extractor/issues/28) | ## Running diff --git a/main.py b/main.py index 55a5d6a..33cd1bd 100644 --- a/main.py +++ b/main.py @@ -466,7 +466,7 @@ def get_save_paths( temp_folder = Path(temp_dir.name) / "P3R" temp_folder.mkdir() - key = "ae5zeitaix1joowooNgie3fahP5Ohph" #Key used to encrypt/decrypt the savefile + key = "ae5zeitaix1joowooNgie3fahP5Ohph" # Key used to encrypt/decrypt the savefile key_length = len(key) # Encryption method