Seregon/ShadPKG

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

C++/47.3 KB/No license
core/patcher/psn_bypass.cpp
ShadPKG / core / patcher / psn_bypass.cpp
1#include "psn_bypass.h"
2#include <algorithm>
3#include <cstring>
4#include <fstream>
5#include <iostream>
6 
7namespace ShadPKG::Patcher {
8 
9// Known PSN function signatures for PS4 (x86-64)
10const std::vector<std::pair<std::string, std::vector<uint8_t>>>
11 PSNBypass::psnSignatures_ = {
12 // sceNpCheckPlus - Check PS Plus subscription
13 {"sceNpCheckPlus",
14 {0x48, 0x89, 0x5C, 0x24, 0x08, 0x57, 0x48, 0x83, 0xEC, 0x20}},
15 
16 // sceUserServiceGetNpAccountId - Get PSN account ID
17 {"sceUserServiceGetNpAccountId",
18 {0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18}},
19 
20 // sceNpCheckCallback - PSN callback check
21 {"sceNpCheckCallback",
22 {0x40, 0x53, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x8B, 0xD9}},
23 
24 // sceNpGetOnlineId - Get online ID
25 {"sceNpGetOnlineId",
26 {0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10}},
27 
28 // Generic PSN auth check pattern
29 {"PSN_Auth_Check", {0x85, 0xC0, 0x74}}, // test eax, eax; jz
30 
31 // sceNpManagerGetNpId
32 {"sceNpManagerGetNpId", {0x48, 0x8B, 0xC4, 0x48, 0x89, 0x58, 0x08}},
33};
34 
35PSNBypass::PSNBypass() {}
36 
37bool PSNBypass::loadEboot(const std::string &path) {
38 std::ifstream file(path, std::ios::binary | std::ios::ate);
39 if (!file.is_open()) {
40 std::cerr << "[PSNBypass] Failed to open: " << path << std::endl;
41 return false;
42 }
43 
44 std::streamsize size = file.tellg();
45 file.seekg(0, std::ios::beg);
46 
47 ebootData_.resize(size);
48 if (!file.read(reinterpret_cast<char *>(ebootData_.data()), size)) {
49 std::cerr << "[PSNBypass] Failed to read file" << std::endl;
50 return false;
51 }
52 
53 loadedPath_ = path;
54 isLoaded_ = true;
55 std::cout << "[PSNBypass] Loaded eboot: " << path << " (" << size << " bytes)"
56 << std::endl;
57 return true;
58}
59 
60bool PSNBypass::saveEboot(const std::string &outputPath) {
61 if (!isLoaded_) {
62 std::cerr << "[PSNBypass] No eboot loaded" << std::endl;
63 return false;
64 }
65 
66 std::ofstream file(outputPath, std::ios::binary);
67 if (!file.is_open()) {
68 std::cerr << "[PSNBypass] Failed to create: " << outputPath << std::endl;
69 return false;
70 }
71 
72 file.write(reinterpret_cast<const char *>(ebootData_.data()),
73 ebootData_.size());
74 std::cout << "[PSNBypass] Saved patched eboot: " << outputPath << std::endl;
75 return true;
76}
77 
78std::vector<uint64_t>
79PSNBypass::searchPattern(const std::vector<uint8_t> &pattern,
80 const std::vector<uint8_t> &mask) {
81 std::vector<uint64_t> results;
82 if (!isLoaded_ || pattern.empty())
83 return results;
84 
85 bool useMask = !mask.empty() && mask.size() == pattern.size();
86 
87 for (size_t i = 0; i <= ebootData_.size() - pattern.size(); ++i) {
88 bool match = true;
89 for (size_t j = 0; j < pattern.size(); ++j) {
90 uint8_t maskByte = useMask ? mask[j] : 0xFF;
91 if ((ebootData_[i + j] & maskByte) != (pattern[j] & maskByte)) {
92 match = false;
93 break;
94 }
95 }
96 if (match) {
97 results.push_back(i);
98 }
99 }
100 return results;
101}
102 
103std::vector<uint64_t> PSNBypass::findStringReferences(uint64_t stringOffset) {
104 std::vector<uint64_t> refs;
105 if (!isLoaded_)
106 return refs;
107 
108 // Search for LEA or generic pointers to this string
109 // x86-64 LEA is usually: 48 8D xx [destination] [offset relative to RIP]
110 // RIP = instruction_addr + instruction_length
111 // target = RIP + offset
112 // offset = target - RIP
113 
114 // Scan the entire text section (approximated as the whole file for now)
115 // We look for 4-byte values that satisfy: current_offset + 7 + value =
116 // stringOffset (LEA is 7 bytes usually) Or just scan for 4-byte values: value
117 // = stringOffset - (current_offset + 7)
118 
119 // Checking standard LEA r64, [rip + disp32] -> 48 8d 3d / 15 / 05 ...
120 // Opcode can be 48 8d ...
121 
122 // Optimization: Just scan for the 32-bit displacement
123 // displacement = stringOffset - (instruction_addr + 7)
124 
125 for (size_t i = 0; i < ebootData_.size() - 7; ++i) {
126 // Assume LEA instruction length is 7 bytes (common for 64-bit address load)
127 int32_t disp =
128 static_cast<int32_t>(stringOffset) - static_cast<int32_t>(i + 7);
129 
130 // Check if bytes at i+3 match this displacement (LEA opcode is 3 bytes: 48
131 // 8D xx)
132 if (ebootData_[i] == 0x48 && ebootData_[i + 1] == 0x8D) {
133 int32_t foundDisp = 0;
134 std::memcpy(&foundDisp, &ebootData_[i + 3], 4);
135 if (foundDisp == disp) {
136 refs.push_back(i);
137 }
138 }
139 }
140 return refs;
141}
142 
143std::vector<uint64_t> PSNBypass::findPSNFunctions() {
144 std::vector<uint64_t> allResults;
145 
146 for (const auto &[name, sig] : psnSignatures_) {
147 auto results = searchPattern(sig);
148 for (auto offset : results) {
149 std::cout << "[PSNBypass] Found " << name << " at offset 0x" << std::hex
150 << offset << std::dec << std::endl;
151 allResults.push_back(offset);
152 }
153 }
154 
155 // Search for string references to PSN-related strings
156 std::vector<std::string> psnStrings = {
157 "PlayStation Network", "PSN", "sceNp", "NpManager", "SignIn",
158 "PlayStation Plus", "PS Plus"};
159 
160 for (const auto &str : psnStrings) {
161 std::vector<uint8_t> strBytes(str.begin(), str.end());
162 auto results = searchPattern(strBytes);
163 for (auto offset : results) {
164 std::cout << "[PSNBypass] Found string \"" << str << "\" at offset 0x"
165 << std::hex << offset << std::dec << std::endl;
166 
167 // Find references to this string!
168 auto refs = findStringReferences(offset);
169 for (auto refOffset : refs) {
170 std::cout << "[PSNBypass] -> Referenced at 0x" << std::hex
171 << refOffset << std::dec << " (LEA/Ptr)" << std::endl;
172 }
173 }
174 }
175 
176 return allResults;
177}
178 
179bool PSNBypass::verifyPatch(const PatchEntry &patch) {
180 if (!isLoaded_)
181 return false;
182 if (patch.offset + patch.original.size() > ebootData_.size())
183 return false;
184 
185 for (size_t i = 0; i < patch.original.size(); ++i) {
186 if (ebootData_[patch.offset + i] != patch.original[i]) {
187 return false;
188 }
189 }
190 return true;
191}
192 
193bool PSNBypass::applyPatch(const PatchEntry &patch) {
194 if (!isLoaded_) {
195 std::cerr << "[PSNBypass] No eboot loaded" << std::endl;
196 return false;
197 }
198 
199 if (patch.offset + patch.patched.size() > ebootData_.size()) {
200 std::cerr << "[PSNBypass] Patch offset out of bounds" << std::endl;
201 return false;
202 }
203 
204 // Verify original bytes if provided
205 if (!patch.original.empty() && !verifyPatch(patch)) {
206 std::cerr << "[PSNBypass] Original bytes mismatch for patch: " << patch.name
207 << std::endl;
208 return false;
209 }
210 
211 // Apply patch
212 std::memcpy(ebootData_.data() + patch.offset, patch.patched.data(),
213 patch.patched.size());
214 std::cout << "[PSNBypass] Applied patch: " << patch.name << " at 0x"
215 << std::hex << patch.offset << std::dec << std::endl;
216 return true;
217}
218 
219bool PSNBypass::applyPatchSet(const GamePatchSet &patchSet) {
220 std::cout << "[PSNBypass] Applying patch set for " << patchSet.gameName
221 << " (" << patchSet.gameId << " v" << patchSet.version << ")"
222 << std::endl;
223 
224 int applied = 0;
225 for (const auto &patch : patchSet.patches) {
226 if (applyPatch(patch)) {
227 applied++;
228 }
229 }
230 
231 std::cout << "[PSNBypass] Applied " << applied << "/"
232 << patchSet.patches.size() << " patches" << std::endl;
233 return applied > 0;
234}
235 
236bool PSNBypass::autoDetectAndPatch() {
237 if (!isLoaded_)
238 return false;
239 
240 // Try to find PSN functions and patch them
241 auto psnFuncs = findPSNFunctions();
242 
243 if (psnFuncs.empty()) {
244 std::cout << "[PSNBypass] No PSN functions found via signatures"
245 << std::endl;
246 }
247 
248 // Try Minecraft-specific patches
249 auto mcPatches = getMinecraftPatches("01.00");
250 return applyPatchSet(mcPatches);
251}
252 
253std::vector<uint8_t> PSNBypass::createNopSled(size_t size) {
254 return std::vector<uint8_t>(size, 0x90);
255}
256 
257std::vector<uint8_t> PSNBypass::createReturnZero() {
258 // xor eax, eax (31 C0) ; ret (C3)
259 return {0x31, 0xC0, 0xC3};
260}
261 
262std::vector<uint8_t> PSNBypass::createReturnOne() {
263 // mov eax, 1 (B8 01 00 00 00) ; ret (C3)
264 return {0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3};
265}
266 
267GamePatchSet PSNBypass::getMinecraftPatches(const std::string &version) {
268 GamePatchSet patchSet;
269 patchSet.gameId = "CUSA00265";
270 patchSet.gameName = "Minecraft: PlayStation 4 Edition";
271 patchSet.version = version;
272 
273 // These patches are based on known Minecraft PS4 bypass techniques
274 // The actual offsets need to be found by analyzing the specific eboot version
275 
276 if (version == "01.00" || version == "1.00") {
277 // Patch 1: Skip PSN sign-in check
278 // This typically involves finding the sceUserServiceGetNpAccountId call
279 // and making it return success (0) without actually checking
280 patchSet.patches.push_back(
281 {"PSN_SignIn_Bypass",
282 "Bypass PSN sign-in requirement (Force SignIn Flag)",
283 0x108414, // Offset verified: C6 83 D0 01 00 00 00 -> 00 is at 0x108414
284 {0x00}, // Original byte (MOV BYTE PTR [RBX+1D0], 0)
285 {0x01}, // Patched byte (MOV BYTE PTR [RBX+1D0], 1)
286 PatchType::CUSTOM});
287 
288 // Patch 2: Skip PS Plus check for multiplayer (Require finding real offset)
289 /*
290 patchSet.patches.push_back({"PSPlus_Check_Bypass",
291 "Bypass PS Plus requirement for multiplayer",
292 0x0, // Offset needs to be found
293 {},
294 createReturnOne(), // Return 1 = has PS Plus
295 PatchType::RET_ONE});
296 
297 // Patch 3: Enable offline multiplayer
298 patchSet.patches.push_back({"Offline_Multiplayer",
299 "Enable multiplayer without PSN connection",
300 0x0,
301 {},
302 {0xEB}, // JMP (unconditional) instead of JZ/JNZ
303 PatchType::JMP_ALWAYS});
304 */
305 }
306 
307 // For version 2.35 (the one NELUxPzzz patched)
308 if (version == "02.35" || version == "2.35") {
309 // Known working patches for v2.35
310 // These would be the actual offsets from the patched PKG
311 patchSet.patches.push_back({"PSN_Auth_Bypass_v235",
312 "Bypass PSN authentication (v2.35)",
313 0x0,
314 {},
315 createReturnZero(),
316 PatchType::RET_ZERO});
317 }
318 
319 return patchSet;
320}
321 
322std::string PSNBypass::generateMinecraftOptions() {
323 // Generate the options.txt content for Minecraft PSN bypass
324 // Based on the psxhax thread configuration
325 return R"(mp_username:Steve
326game_difficulty_new:1
327game_thirdperson:0
328gfx_dpadscale:0.5
329mp_server_visible:1
330mp_xboxlive_visible:1
331mp_nex_visible:1
332mp_psn_visible:1
333game_haseverloggedintoxbl:0
334game_haschosennottosignintoxbl:0
335crossplatform_toggle:1
336last_xuid:2535414620388275
337do_not_show_multiplayer_online_safety_warning:1
338game_shownplatformnetworkconnect:1
339)";
340}
341 
342std::vector<GamePatchSet> PSNBypass::getAvailablePatchSets() {
343 std::vector<GamePatchSet> sets;
344 
345 // Minecraft patches
346 sets.push_back(getMinecraftPatches("01.00"));
347 sets.push_back(getMinecraftPatches("02.35"));
348 
349 return sets;
350}
351 
352bool PSNBypass::patchOffset(uint64_t offset,
353 const std::vector<uint8_t> &bytes) {
354 if (!isLoaded_) {
355 std::cerr << "[PSNBypass] No eboot loaded" << std::endl;
356 return false;
357 }
358 
359 if (offset + bytes.size() > ebootData_.size()) {
360 std::cerr << "[PSNBypass] Patch offset out of bounds: 0x" << std::hex
361 << offset << std::dec << std::endl;
362 return false;
363 }
364 
365 // Apply patch
366 std::memcpy(ebootData_.data() + offset, bytes.data(), bytes.size());
367 return true;
368}
369 
370} // namespace ShadPKG::Patcher
371