A tool for deriving PKG packet encryption keys for ps4 written in c++
| 1 | /* |
| 2 | * ╔═══════════════════════════════════════════════════════════════════════════╗ |
| 3 | * ║ PS4 MEMORY EMULATION - IMPLEMENTATION ║ |
| 4 | * ╠═══════════════════════════════════════════════════════════════════════════╣ |
| 5 | * ║ Uses mmap to allocate a region that simulates PS4 global address space. ║ |
| 6 | * ╚═══════════════════════════════════════════════════════════════════════════╝ |
| 7 | */ |
| 8 | |
| 9 | #include "ps4_memory.h" |
| 10 | #include <cmath> |
| 11 | #include <cstring> |
| 12 | #include <iostream> |
| 13 | |
| 14 | #ifdef __APPLE__ |
| 15 | #include <mach/mach.h> |
| 16 | #include <sys/mman.h> |
| 17 | #elif defined(__linux__) |
| 18 | #include <sys/mman.h> |
| 19 | #elif defined(_WIN32) |
| 20 | #include <windows.h> |
| 21 | #endif |
| 22 | |
| 23 | namespace PS4Emu { |
| 24 | |
| 25 | // Global memory block pointer |
| 26 | static uint8_t *g_ps4GlobalMemory = nullptr; |
| 27 | static bool g_initialized = false; |
| 28 | |
| 29 | /* |
| 30 | * ┌─────────────────────────────────────────────────────────────────┐ |
| 31 | * │ PLATFORM-SPECIFIC MMAP │ |
| 32 | * └─────────────────────────────────────────────────────────────────┘ |
| 33 | */ |
| 34 | |
| 35 | static void *AllocateMemoryBlock(size_t size) { |
| 36 | #if defined(__APPLE__) || defined(__linux__) |
| 37 | // Use mmap with MAP_ANONYMOUS for a zeroed memory block |
| 38 | void *ptr = mmap(nullptr, // Let OS choose address |
| 39 | size, |
| 40 | PROT_READ | PROT_WRITE, // Read/write access |
| 41 | MAP_PRIVATE | MAP_ANONYMOUS, // Private anonymous mapping |
| 42 | -1, // No file descriptor |
| 43 | 0 // No offset |
| 44 | ); |
| 45 | |
| 46 | if (ptr == MAP_FAILED) { |
| 47 | std::cerr << "[PS4Emu] ERROR: mmap failed for global memory!" << std::endl; |
| 48 | return nullptr; |
| 49 | } |
| 50 | return ptr; |
| 51 | |
| 52 | #elif defined(_WIN32) |
| 53 | void *ptr = |
| 54 | VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); |
| 55 | if (!ptr) { |
| 56 | std::cerr << "[PS4Emu] ERROR: VirtualAlloc failed for global memory!" |
| 57 | << std::endl; |
| 58 | } |
| 59 | return ptr; |
| 60 | #else |
| 61 | #error "Unsupported platform" |
| 62 | #endif |
| 63 | } |
| 64 | |
| 65 | static void FreeMemoryBlock(void *ptr, size_t size) { |
| 66 | #if defined(__APPLE__) || defined(__linux__) |
| 67 | munmap(ptr, size); |
| 68 | #elif defined(_WIN32) |
| 69 | VirtualFree(ptr, 0, MEM_RELEASE); |
| 70 | #endif |
| 71 | } |
| 72 | |
| 73 | /* |
| 74 | * ┌─────────────────────────────────────────────────────────────────┐ |
| 75 | * │ PUBLIC API IMPLEMENTATION │ |
| 76 | * └─────────────────────────────────────────────────────────────────┘ |
| 77 | */ |
| 78 | |
| 79 | bool InitializeMemory() { |
| 80 | if (g_initialized) { |
| 81 | std::cerr << "[PS4Emu] Warning: Memory already initialized" << std::endl; |
| 82 | return true; |
| 83 | } |
| 84 | |
| 85 | std::cout << "[PS4Emu] Initializing PS4 global memory emulation..." |
| 86 | << std::endl; |
| 87 | std::cout << "[PS4Emu] Allocating " << (PS4_GLOBAL_SIZE / 1024 / 1024) |
| 88 | << "MB for globals" << std::endl; |
| 89 | |
| 90 | g_ps4GlobalMemory = |
| 91 | static_cast<uint8_t *>(AllocateMemoryBlock(PS4_GLOBAL_SIZE)); |
| 92 | |
| 93 | if (!g_ps4GlobalMemory) { |
| 94 | return false; |
| 95 | } |
| 96 | |
| 97 | // Zero-initialize the entire block |
| 98 | std::memset(g_ps4GlobalMemory, 0, PS4_GLOBAL_SIZE); |
| 99 | |
| 100 | g_initialized = true; |
| 101 | std::cout << "[PS4Emu] Global memory ready at host address: " |
| 102 | << static_cast<void *>(g_ps4GlobalMemory) << std::endl; |
| 103 | |
| 104 | // ┌─────────────────────────────────────────────────────────────────────────┐ |
| 105 | // │ INITIALIZE CRITICAL GLOBAL STUBS │ |
| 106 | // │ These addresses are heavily accessed and need valid pointers │ |
| 107 | // └─────────────────────────────────────────────────────────────────────────┘ |
| 108 | |
| 109 | // Create a "safe zone" for dummy objects at the end of our region |
| 110 | // Each dummy object is 256 bytes to hold a minimal VTable-like structure |
| 111 | constexpr size_t DUMMY_OBJECT_SIZE = 256; |
| 112 | constexpr size_t SAFE_ZONE_START = |
| 113 | PS4_GLOBAL_SIZE - (1024 * 1024); // Last 1MB |
| 114 | static size_t nextDummyOffset = SAFE_ZONE_START; |
| 115 | |
| 116 | auto allocateDummy = [&]() -> uint64_t { |
| 117 | uint64_t offset = nextDummyOffset; |
| 118 | nextDummyOffset += DUMMY_OBJECT_SIZE; |
| 119 | |
| 120 | // Get the HOST address of this dummy object |
| 121 | uint8_t *hostAddr = g_ps4GlobalMemory + offset; |
| 122 | uint64_t hostAddrValue = reinterpret_cast<uint64_t>(hostAddr); |
| 123 | |
| 124 | // Initialize the dummy with HOST pointers to itself |
| 125 | // This allows chained dereferences to work correctly |
| 126 | uint64_t *ptr = reinterpret_cast<uint64_t *>(hostAddr); |
| 127 | for (size_t i = 0; i < DUMMY_OBJECT_SIZE / sizeof(uint64_t); i++) { |
| 128 | ptr[i] = hostAddrValue; // Each slot points back to this same object (host addr) |
| 129 | } |
| 130 | |
| 131 | return hostAddrValue; // Return the HOST address, not the offset |
| 132 | }; |
| 133 | |
| 134 | // List of critical globals that need initialization |
| 135 | // Format: {address, description} |
| 136 | struct GlobalInit { |
| 137 | uint64_t addr; |
| 138 | const char *desc; |
| 139 | }; |
| 140 | |
| 141 | GlobalInit criticalGlobals[] = { |
| 142 | {0x995e78, "Main game context (36k refs)"}, |
| 143 | {0x9bb298, "Object pool (4k refs)"}, |
| 144 | {0xa21da8, "System table A"}, |
| 145 | {0xa21d98, "System table B"}, |
| 146 | {0x949e38, "Runtime state"}, |
| 147 | {0xa21d88, "System table C"}, |
| 148 | {0x995e60, "Engine pointer A"}, |
| 149 | {0x995e68, "Engine pointer B"}, |
| 150 | {0xa21d90, "System table D"}, |
| 151 | {0xa21d80, "System table E"}, |
| 152 | {0xa21da0, "System table F"}, |
| 153 | {0x925500, "Resource manager A"}, |
| 154 | {0x9254b0, "Resource manager B"}, |
| 155 | {0x925420, "Resource manager C"}, |
| 156 | {0x9d00d4, "Config value"}, |
| 157 | {0x9bc3d0, "Memory allocator"}, |
| 158 | {0x99a9e8, "Scene manager"}, |
| 159 | {0x9992b0, "Render context"}, |
| 160 | {0x96d060, "Audio manager"}, |
| 161 | {0x959900, "Input manager"}, |
| 162 | {0x9d9e28, "TLS context"}, // The one causing the first crash |
| 163 | {0x9d9e20, "TLS pointer A"}, |
| 164 | {0x9d9e22, "TLS pointer B"}, |
| 165 | {0x961800, "Module table A"}, |
| 166 | {0x961850, "Module table B"}, |
| 167 | {0x995e40, "Global State A"}, |
| 168 | {0x997420, "Global State B"}, |
| 169 | {0x997438, "Global State C"}, |
| 170 | {0x99743a, "Global State D"}, |
| 171 | {0x997430, "Global State E"}, |
| 172 | // Additional managers discovered during runtime |
| 173 | {0x9bb2a0, "Object Pool B"}, |
| 174 | {0x9bb2a8, "Object Pool C"}, |
| 175 | {0x99a9f0, "Scene Manager B"}, |
| 176 | {0x99a9f8, "Scene Manager C"}, |
| 177 | {0x9992b8, "Render Context B"}, |
| 178 | {0x9992c0, "Render Context C"}, |
| 179 | {0x96d068, "Audio Manager B"}, |
| 180 | {0x96d070, "Audio Manager C"}, |
| 181 | {0x959908, "Input Manager B"}, |
| 182 | {0x959910, "Input Manager C"}, |
| 183 | {0x959918, "Input State"}, |
| 184 | {0x9bc3d8, "Memory Allocator B"}, |
| 185 | {0x9bc3e0, "Memory Allocator C"}, |
| 186 | {0x9d00d8, "Config Value B"}, |
| 187 | {0x9d00e0, "Config Value C"}, |
| 188 | // Frame timing and loop control |
| 189 | {0x9d9e30, "Frame Counter"}, |
| 190 | {0x9d9e38, "Delta Time Ptr"}, |
| 191 | {0x9d9e40, "Last Frame Time"}, |
| 192 | // Additional critical pointers from crash analysis |
| 193 | {0x961858, "Module Table C"}, |
| 194 | {0x961860, "Module Table D"}, |
| 195 | {0x995e80, "Engine Pointer C"}, |
| 196 | {0x995e88, "Engine Pointer D"}, |
| 197 | }; |
| 198 | |
| 199 | std::cout << "[PS4Emu] Initializing " |
| 200 | << (sizeof(criticalGlobals) / sizeof(GlobalInit)) |
| 201 | << " critical global stubs..." << std::endl; |
| 202 | |
| 203 | for (const auto &g : criticalGlobals) { |
| 204 | uint64_t dummyAddr = allocateDummy(); |
| 205 | // Write the dummy address into the global slot |
| 206 | *reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + g.addr) = dummyAddr; |
| 207 | } |
| 208 | |
| 209 | std::cout << "[PS4Emu] Global stubs initialized successfully" << std::endl; |
| 210 | |
| 211 | // ┌─────────────────────────────────────────────────────────────────────────┐ |
| 212 | // │ INITIALIZE TIMING CONSTANTS │ |
| 213 | // │ These are float values used in frame timing calculations │ |
| 214 | // │ Without them, loops multiply by 0 forever │ |
| 215 | // └─────────────────────────────────────────────────────────────────────────┘ |
| 216 | |
| 217 | // Frame timing values (addresses 0x7d69c4 - 0x7d69d4) |
| 218 | // These appear to be reciprocals of loop iteration counts |
| 219 | float timingConstants[] = { |
| 220 | 1.0f / 65536.0f, // 0x7d69c4 - reciprocal of 0x10000 |
| 221 | 65536.0f, // 0x7d69c8 - loop count |
| 222 | 1.0f / 65536.0f, // 0x7d69cc - reciprocal of 0x10000 (causes loop!) |
| 223 | 65536.0f, // 0x7d69d0 - loop count |
| 224 | 0.5f, // 0x7d69d4 - offset |
| 225 | }; |
| 226 | |
| 227 | uint32_t timingAddrs[] = {0x7d69c4, 0x7d69c8, 0x7d69cc, 0x7d69d0, 0x7d69d4}; |
| 228 | |
| 229 | for (size_t i = 0; i < 5; i++) { |
| 230 | *reinterpret_cast<float *>(g_ps4GlobalMemory + timingAddrs[i]) = |
| 231 | timingConstants[i]; |
| 232 | } |
| 233 | |
| 234 | // Additional float constants found in the code |
| 235 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x7cb0f4) = 1.0f; |
| 236 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x7cb0f8) = 1.0f; |
| 237 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x7d6a50) = 1.0f; |
| 238 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x7d6a54) = 1.0f; |
| 239 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x7d6a58) = 1.0f; |
| 240 | |
| 241 | std::cout << "[PS4Emu] Timing constants initialized" << std::endl; |
| 242 | |
| 243 | // ┌─────────────────────────────────────────────────────────────────────────┐ |
| 244 | // │ PRE-POPULATE TRIG TABLES │ |
| 245 | // │ The decompiler corrupted the loop that builds sine/cosine tables. │ |
| 246 | // │ We pre-populate them here so the game skips the broken init code. │ |
| 247 | // └─────────────────────────────────────────────────────────────────────────┘ |
| 248 | |
| 249 | // Allocate trig table: 65536 entries * 4 bytes = 256KB |
| 250 | constexpr size_t TRIG_TABLE_ENTRIES = 0x10000; |
| 251 | constexpr size_t TRIG_TABLE_SIZE = TRIG_TABLE_ENTRIES * sizeof(float); |
| 252 | constexpr size_t TRIG_TABLE_OFFSET = SAFE_ZONE_START - TRIG_TABLE_SIZE; |
| 253 | |
| 254 | float *trigTable = |
| 255 | reinterpret_cast<float *>(g_ps4GlobalMemory + TRIG_TABLE_OFFSET); |
| 256 | |
| 257 | // Fill with sin/cos values (common game engine lookup table) |
| 258 | for (size_t i = 0; i < TRIG_TABLE_ENTRIES; i++) { |
| 259 | float angle = (float(i) / TRIG_TABLE_ENTRIES) * 2.0f * 3.14159265358979f; |
| 260 | trigTable[i] = std::sin(angle); |
| 261 | } |
| 262 | |
| 263 | // Store pointer to trig table at 0x9d9e28 (the address the loops check) |
| 264 | // This makes reg_rax valid so the check passes and loops are skipped |
| 265 | *reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + 0x9d9e28) = |
| 266 | reinterpret_cast<uint64_t>(trigTable); |
| 267 | |
| 268 | std::cout << "[PS4Emu] Trig tables pre-populated (" << TRIG_TABLE_ENTRIES |
| 269 | << " entries)" << std::endl; |
| 270 | |
| 271 | // ┌─────────────────────────────────────────────────────────────────────────┐ |
| 272 | // │ INITIALIZE FRAME COUNTER FOR DEBUGGING │ |
| 273 | // │ This allows us to track if the main loop is actually running │ |
| 274 | // └─────────────────────────────────────────────────────────────────────────┘ |
| 275 | |
| 276 | // Frame counter at a known location for debugging |
| 277 | *reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + 0x9d9e30) = 0; |
| 278 | |
| 279 | // Delta time initialized to 16.67ms (60 FPS) |
| 280 | *reinterpret_cast<float *>(g_ps4GlobalMemory + 0x9d9e38) = 0.01667f; |
| 281 | |
| 282 | // Last frame time |
| 283 | *reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + 0x9d9e40) = 0; |
| 284 | |
| 285 | std::cout << "[PS4Emu] Frame counter initialized at 0x9d9e30" << std::endl; |
| 286 | |
| 287 | return true; |
| 288 | } |
| 289 | |
| 290 | void ShutdownMemory() { |
| 291 | if (g_ps4GlobalMemory) { |
| 292 | std::cout << "[PS4Emu] Shutting down memory emulation..." << std::endl; |
| 293 | FreeMemoryBlock(g_ps4GlobalMemory, PS4_GLOBAL_SIZE); |
| 294 | g_ps4GlobalMemory = nullptr; |
| 295 | g_initialized = false; |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | void *TranslateAddress(uint64_t ps4_addr) { |
| 300 | // ═══════════════════════════════════════════════════════════════════ |
| 301 | // LOOP DETECTION: Count calls and exit after limit |
| 302 | // ═══════════════════════════════════════════════════════════════════ |
| 303 | static uint64_t s_call_count = 0; |
| 304 | static constexpr uint64_t LOG_INTERVAL = 1000000; // Log every 1M calls |
| 305 | static constexpr uint64_t CALL_LIMIT = 50000000; // Exit after 50M calls |
| 306 | |
| 307 | s_call_count++; |
| 308 | |
| 309 | if (s_call_count % LOG_INTERVAL == 0) { |
| 310 | std::cout << "[PS4Emu] TranslateAddress calls: " << s_call_count |
| 311 | << " (last addr: 0x" << std::hex << ps4_addr << std::dec << ")" |
| 312 | << std::endl; |
| 313 | } |
| 314 | |
| 315 | if (s_call_count >= CALL_LIMIT) { |
| 316 | std::cerr << "[PS4Emu] LIMIT REACHED: " << CALL_LIMIT |
| 317 | << " memory accesses. Exiting to prevent hang." << std::endl; |
| 318 | std::exit(1); |
| 319 | } |
| 320 | |
| 321 | // Safety check: ensure memory is initialized |
| 322 | if (!g_initialized || !g_ps4GlobalMemory) { |
| 323 | std::cerr << "[PS4Emu] ERROR: Accessing global memory before " |
| 324 | "initialization! addr=0x" |
| 325 | << std::hex << ps4_addr << std::endl; |
| 326 | return nullptr; |
| 327 | } |
| 328 | |
| 329 | // Check if address is within the PS4 global range |
| 330 | if (ps4_addr >= PS4_GLOBAL_SIZE) { |
| 331 | // Silent remapping - too noisy otherwise |
| 332 | ps4_addr = ps4_addr % PS4_GLOBAL_SIZE; |
| 333 | } |
| 334 | |
| 335 | // Translate: PS4 address -> our allocated block |
| 336 | return g_ps4GlobalMemory + ps4_addr; |
| 337 | } |
| 338 | |
| 339 | // ┌─────────────────────────────────────────────────────────────────────────┐ |
| 340 | // │ DEBUG: Get frame counter to check if main loop is running │ |
| 341 | // └─────────────────────────────────────────────────────────────────────────┘ |
| 342 | uint64_t GetFrameCounter() { |
| 343 | if (!g_initialized || !g_ps4GlobalMemory) { |
| 344 | return 0; |
| 345 | } |
| 346 | return *reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + 0x9d9e30); |
| 347 | } |
| 348 | |
| 349 | void IncrementFrameCounter() { |
| 350 | if (!g_initialized || !g_ps4GlobalMemory) { |
| 351 | return; |
| 352 | } |
| 353 | uint64_t *counter = reinterpret_cast<uint64_t *>(g_ps4GlobalMemory + 0x9d9e30); |
| 354 | (*counter)++; |
| 355 | |
| 356 | // Log every 60 frames (approximately 1 second at 60 FPS) |
| 357 | if (*counter % 60 == 0) { |
| 358 | std::cout << "[PS4Emu] Frame: " << *counter << std::endl; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | void *GetGlobalMemoryBase() { |
| 363 | return g_ps4GlobalMemory; |
| 364 | } |
| 365 | |
| 366 | } // namespace PS4Emu |
| 367 |