Seregon/ShadPKG

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

C++/47.3 KB/No license
core/decompiler/DecompilerContext.cpp
ShadPKG / core / decompiler / DecompilerContext.cpp
1#include "DecompilerContext.h"
2#include "analysis/MemberAccessAnalysis.h"
3#include "common/logging/log.h"
4#include <capstone/capstone.h>
5#include <chrono>
6#include <cstdlib>
7#include <cstring>
8#include <filesystem>
9#include <fstream>
10#include <iomanip>
11#include <iostream>
12#include <queue>
13#include <set>
14#include <sstream>
15#define JSON_ASSERT(x)
16#include <nlohmann/json.hpp>
17using json = nlohmann::json;
18 
19namespace ShadPKG::Decompiler {
20 
21void DecompilerContext::LoadBinary(const std::vector<uint8_t> &data,
22 uint64_t baseAddress) {
23 rawData_ = data;
24 baseAddress_ = baseAddress;
25 isAnalyzed_ = false;
26 functions_.clear();
27 // Initialize SymbolDatabase
28 symbolDatabase_ = std::make_shared<Analysis::SymbolDatabase>();
29}
30 
31bool DecompilerContext::LoadELF(const std::vector<uint8_t> &data) {
32 if (data.size() < 64)
33 return false;
34 
35 size_t elfOffset = 0;
36 bool found = false;
37 
38 // Check Magic: 7F 45 4C 46
39 if (data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F') {
40 found = true;
41 } else {
42 // Search for it (limit search to first 4KB)
43 for (size_t i = 0; i < std::min(data.size() - 4, (size_t)0x1000); ++i) {
44 if (data[i] == 0x7F && data[i + 1] == 'E' && data[i + 2] == 'L' &&
45 data[i + 3] == 'F') {
46 elfOffset = i;
47 found = true;
48 break;
49 }
50 }
51 }
52 
53 if (!found) {
54 LOG_ERROR(Common, "No ELF header found in data.");
55 return false;
56 }
57 
58 elfOffset_ = elfOffset;
59 const uint8_t *elfBase = data.data() + elfOffset_;
60 
61 // Parse ELF Header (64-bit)
62 uint64_t entryPoint = 0;
63 uint64_t phOff = 0;
64 uint16_t phEntSize = 0;
65 uint16_t phNum = 0;
66 uint64_t shOff = 0;
67 uint16_t shEntSize = 0;
68 uint16_t shNum = 0;
69 uint16_t shStrNdx = 0;
70 
71 if (data.size() < elfOffset + 64)
72 return false;
73 
74 std::memcpy(&entryPoint, elfBase + 0x18, 8);
75 std::memcpy(&phOff, elfBase + 0x20, 8);
76 std::memcpy(&phEntSize, elfBase + 0x36, 2);
77 std::memcpy(&phNum, elfBase + 0x38, 2);
78 std::memcpy(&shOff, elfBase + 0x28, 8);
79 std::memcpy(&shEntSize, elfBase + 0x3A, 2);
80 std::memcpy(&shNum, elfBase + 0x3C, 2);
81 std::memcpy(&shStrNdx, elfBase + 0x3E, 2);
82 // Capture entry point for later use in ExportProject
83 entryPoint_ = entryPoint;
84 
85 LOG_INFO(Common,
86 "ELF Found at offset 0x{:X}. Entry=0x{:X}, PHOff=0x{:X}, PHNum={}",
87 elfOffset, entryPoint, phOff, phNum);
88 
89 segments_.clear();
90 uint64_t minVA = UINT64_MAX;
91 
92 // Parse Program Headers
93 for (int i = 0; i < phNum; ++i) {
94 size_t currentPhOffset = phOff + (i * phEntSize);
95 if (elfOffset + currentPhOffset + phEntSize > data.size())
96 break;
97 
98 const uint8_t *phBase = elfBase + currentPhOffset;
99 
100 uint32_t p_type;
101 uint32_t p_flags;
102 uint64_t p_offset;
103 uint64_t p_vaddr;
104 uint64_t p_filesz;
105 uint64_t p_memsz;
106 
107 std::memcpy(&p_type, phBase, 4);
108 std::memcpy(&p_flags, phBase + 4, 4);
109 std::memcpy(&p_offset, phBase + 8, 8);
110 std::memcpy(&p_vaddr, phBase + 0x10, 8);
111 std::memcpy(&p_filesz, phBase + 0x20, 8);
112 std::memcpy(&p_memsz, phBase + 0x28, 8);
113 
114 if (p_type == 1) { // PT_LOAD
115 Segment seg;
116 seg.virtualAddress = p_vaddr;
117 // Absolute offset in rawData_
118 seg.fileOffset = elfOffset + p_offset;
119 seg.size = p_filesz;
120 seg.memSize = p_memsz; // Include BSS zero-padding
121 segments_.push_back(seg);
122 
123 if (p_vaddr < minVA)
124 minVA = p_vaddr;
125 
126 LOG_INFO(Common,
127 "Segment: VA=0x{:X}, Offset=0x{:X} (Abs: 0x{:X}), Size=0x{:X}",
128 p_vaddr, p_offset, seg.fileOffset, p_filesz);
129 } else if (p_type == 3) { // PT_DYNAMIC
130 // Parse .dynamic section to find DT_INIT_ARRAY and DT_INIT_ARRAYSZ
131 LOG_INFO(Common, "PT_DYNAMIC found at VA=0x{:X}, offset=0x{:X}, size=0x{:X}",
132 p_vaddr, p_offset, p_filesz);
133
134 if (elfOffset + p_offset + p_filesz <= data.size()) {
135 const uint8_t *dynBase = elfBase + p_offset;
136 // Parse dynamic entries (16 bytes each: tag + value)
137 for (size_t dynOff = 0; dynOff < p_filesz; dynOff += 16) {
138 uint64_t d_tag = 0;
139 uint64_t d_val = 0;
140 std::memcpy(&d_tag, dynBase + dynOff, 8);
141 std::memcpy(&d_val, dynBase + dynOff + 8, 8);
142
143 if (d_tag == 0) break; // DT_NULL
144 if (d_tag == 25) { // DT_INIT_ARRAY
145 initArrayAddr_ = d_val;
146 LOG_INFO(Common, "DT_INIT_ARRAY found at VA=0x{:X}", initArrayAddr_);
147 } else if (d_tag == 27) { // DT_INIT_ARRAYSZ
148 initArraySize_ = d_val;
149 LOG_INFO(Common, "DT_INIT_ARRAYSZ = 0x{:X}", initArraySize_);
150 } else if (d_tag == 26) { // DT_FINI_ARRAY
151 finiArrayAddr_ = d_val;
152 LOG_INFO(Common, "DT_FINI_ARRAY found at VA=0x{:X}", finiArrayAddr_);
153 } else if (d_tag == 28) { // DT_FINI_ARRAYSZ
154 finiArraySize_ = d_val;
155 LOG_INFO(Common, "DT_FINI_ARRAYSZ = 0x{:X}", finiArraySize_);
156 }
157 }
158 }
159 }
160 }
161 
162 // Load raw data and set base
163 LoadBinary(data, minVA != UINT64_MAX ? minVA : 0x400000);
164 
165 if (segments_.empty()) {
166 LOG_WARNING(Common, "No PT_LOAD segments found. Falling back to flat "
167 "mapping from ELF base.");
168 Segment seg;
169 seg.virtualAddress = 0x400000;
170 seg.fileOffset = elfOffset;
171 seg.size = data.size() - elfOffset;
172 segments_.push_back(seg);
173 baseAddress_ = 0x400000;
174 }
175 
176 // Parse Section Headers to find .init_array and .fini_array
177 initArrayAddr_ = 0;
178 initArraySize_ = 0;
179 finiArrayAddr_ = 0;
180 finiArraySize_ = 0;
181 
182 LOG_INFO(Common, "Section Header Info: shNum={}, shOff=0x{:X}, shEntSize={}, shStrNdx={}",
183 shNum, shOff, shEntSize, shStrNdx);
184 
185 if (shNum > 0 && shOff > 0) {
186 // Get string table section header first
187 if (shStrNdx < shNum) {
188 size_t strShOffset = shOff + (shStrNdx * shEntSize);
189 if (elfOffset + strShOffset + shEntSize <= data.size()) {
190 const uint8_t *strShBase = elfBase + strShOffset;
191 uint64_t strShAddr = 0;
192 uint64_t strShOffset_file = 0;
193 uint64_t strShSize = 0;
194 std::memcpy(&strShAddr, strShBase + 0x10, 8);
195 std::memcpy(&strShOffset_file, strShBase + 0x18, 8);
196 std::memcpy(&strShSize, strShBase + 0x20, 8);
197 
198 LOG_INFO(Common, "String table: offset=0x{:X}, size=0x{:X}", strShOffset_file, strShSize);
199 
200 // Parse all section headers
201 for (int i = 0; i < shNum; ++i) {
202 size_t shOffset = shOff + (i * shEntSize);
203 if (elfOffset + shOffset + shEntSize > data.size())
204 break;
205 
206 const uint8_t *shBase = elfBase + shOffset;
207 uint32_t sh_name = 0;
208 uint32_t sh_type = 0;
209 uint64_t sh_addr = 0;
210 uint64_t sh_offset = 0;
211 uint64_t sh_size = 0;
212 
213 std::memcpy(&sh_name, shBase, 4);
214 std::memcpy(&sh_type, shBase + 4, 4);
215 std::memcpy(&sh_addr, shBase + 0x10, 8);
216 std::memcpy(&sh_offset, shBase + 0x18, 8);
217 std::memcpy(&sh_size, shBase + 0x20, 8);
218 
219 // Get section name from string table
220 if (sh_name < strShSize && elfOffset + strShOffset_file + sh_name < data.size()) {
221 const char *name = reinterpret_cast<const char *>(
222 data.data() + elfOffset + strShOffset_file + sh_name);
223 std::string sectionName(name);
224 
225 if (sectionName == ".init_array") {
226 initArrayAddr_ = sh_addr;
227 initArraySize_ = sh_size;
228 LOG_INFO(Common, ".init_array found at VA=0x{:X}, size=0x{:X}",
229 initArrayAddr_, initArraySize_);
230 } else if (sectionName == ".fini_array") {
231 finiArrayAddr_ = sh_addr;
232 finiArraySize_ = sh_size;
233 LOG_INFO(Common, ".fini_array found at VA=0x{:X}, size=0x{:X}",
234 finiArrayAddr_, finiArraySize_);
235 }
236 }
237 }
238 }
239 }
240 }
241 
242 return true;
243}
244 
245bool DecompilerContext::VirtualAddressToFileOffset(uint64_t va,
246 uint64_t &offset) const {
247 for (const auto &seg : segments_) {
248 if (va >= seg.virtualAddress && va < seg.virtualAddress + seg.size) {
249 offset = seg.fileOffset + (va - seg.virtualAddress);
250 return true;
251 }
252 }
253 if (segments_.empty() && va >= baseAddress_) {
254 offset = va - baseAddress_;
255 return true;
256 }
257 return false;
258}
259 
260void DecompilerContext::Analyze() {
261 if (rawData_.empty())
262 return;
263 
264 functions_.clear();
265 std::set<uint64_t> visitedGlobal;
266 
267 uint64_t entryPoint = baseAddress_;
268 if (rawData_.size() >= elfOffset_ + 0x20 && rawData_[elfOffset_] == 0x7F &&
269 rawData_[elfOffset_ + 1] == 'E') {
270 std::memcpy(&entryPoint, rawData_.data() + elfOffset_ + 0x18, 8);
271 }
272 
273 std::queue<uint64_t> functionQueue;
274 functionQueue.push(entryPoint);
275 visitedGlobal.insert(entryPoint);
276 
277 uint64_t epOffset = 0;
278 if (VirtualAddressToFileOffset(entryPoint, epOffset)) {
279 if (epOffset + 8 > rawData_.size()) {
280 LOG_ERROR(Common, "Entry Point 0x{:X} is out of file bounds!",
281 entryPoint);
282 return;
283 }
284 } else {
285 LOG_ERROR(Common, "Could not map Entry Point 0x{:X} to file offset!",
286 entryPoint);
287 if (entryPoint >= baseAddress_)
288 epOffset = entryPoint - baseAddress_;
289 }
290 
291 int functionsAnalyzed = 0;
292 const int MAX_FUNCTIONS = 50000;
293 
294 // ┌─────────────────────────────────────────────────────────────────────────┐
295 // │ Run Symbol Analysis early to recover function names from ELF │
296 // │ This populates symbolDatabase_ with names from .dynsym / .rela.plt │
297 // └─────────────────────────────────────────────────────────────────────────┘
298 {
299 auto symbolAnalysis = std::make_shared<Analysis::SymbolAnalysis>(
300 rawData_, baseAddress_, symbolDatabase_);
301 symbolAnalysis->analyze();
302 LOG_INFO(Common, "Symbol Analysis complete: {} symbols loaded.",
303 symbolDatabase_ ? symbolDatabase_->getSymbols().size() : 0);
304 }
305 
306 LOG_INFO(Common, "Starting Function Discovery at 0x{:X}...", entryPoint);
307 
308 // ┌─────────────────────────────────────────────────────────────────────────┐
309 // │ PROLOGUE SCAN FOR ALL FUNCTIONS │
310 // │ Many functions are only called via vtables or indirect jumps. │
311 // │ Scan .text for common x86-64 function prologues: │
312 // │ push rbp; mov rbp, rsp -> 55 48 89 E5 │
313 // │ push rbx -> 53 │
314 // │ sub rsp, N -> 48 83 EC XX / 48 81 EC XX XX XX XX │
315 // └─────────────────────────────────────────────────────────────────────────┘
316 LOG_INFO(Common, "Scanning segments for function prologues...");
317 progress_.currentPhase = "scanning";
318 progress_.prologuesFound = 0;
319 progress_.functionsAnalyzed = 0;
320 progress_.isComplete = false;
321 int prologuesFound = 0;
322 
323 for (const auto &seg : segments_) {
324 if (seg.size < 8)
325 continue;
326 uint64_t scanEnd =
327 std::min(seg.fileOffset + seg.size, (uint64_t)rawData_.size()) - 4;
328 
329 for (uint64_t off = seg.fileOffset; off < scanEnd; ++off) {
330 uint64_t funcVA = seg.virtualAddress + (off - seg.fileOffset);
331 
332 if (visitedGlobal.count(funcVA))
333 continue;
334 
335 // Pattern 1: push rbp; mov rbp, rsp (0x55 0x48 0x89 0xE5)
336 bool match = (rawData_[off] == 0x55 && rawData_[off + 1] == 0x48 &&
337 rawData_[off + 2] == 0x89 && rawData_[off + 3] == 0xE5);
338 
339 // Pattern 2: push rbp; mov rsp variants
340 if (!match && rawData_[off] == 0x55) {
341 // Check for sub rsp, imm8 following (48 83 EC XX)
342 if (off + 5 < scanEnd && rawData_[off + 1] == 0x48 &&
343 rawData_[off + 2] == 0x83 && rawData_[off + 3] == 0xEC) {
344 match = true;
345 }
346 }
347 
348 if (match) {
349 functionQueue.push(funcVA);
350 visitedGlobal.insert(funcVA);
351 prologuesFound++;
352 progress_.prologuesFound = prologuesFound;
353 }
354 }
355 }
356 
357 LOG_INFO(Common, "Found {} candidate functions via prologue scan.",
358 prologuesFound);
359 
360 while (!functionQueue.empty() && functionsAnalyzed < MAX_FUNCTIONS) {
361 uint64_t funcAddr = functionQueue.front();
362 functionQueue.pop();
363 
364 auto func = std::make_shared<IR::Function>();
365 func->address = funcAddr;
366 
367 // ┌─────────────────────────────────────────────────────────────────────────┐
368 // │ Try to recover function name from ELF symbols │
369 // └─────────────────────────────────────────────────────────────────────────┘
370 if (symbolDatabase_) {
371 auto sym = symbolDatabase_->getSymbol(funcAddr);
372 if (sym && !sym->name.empty() &&
373 sym->name.find("sub_") == std::string::npos) {
374 func->name = sym->name;
375 }
376 }
377 if (func->name.empty()) {
378 std::stringstream nameSS;
379 nameSS << "sub_" << std::hex << funcAddr;
380 func->name = nameSS.str();
381 }
382 
383 func->signature = "void " + func->name + "()";
384 func->category = IR::Function::Category::GameLogic;
385 
386 csh handle;
387 if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK)
388 continue;
389 cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
390 
391 std::queue<uint64_t> blockQueue;
392 std::set<uint64_t> visitedBlocks;
393 
394 blockQueue.push(funcAddr);
395 visitedBlocks.insert(funcAddr);
396 
397 while (!blockQueue.empty()) {
398 uint64_t blockAddr = blockQueue.front();
399 blockQueue.pop();
400 
401 auto bb = std::make_shared<IR::BasicBlock>();
402 bb->id = blockAddr;
403 bb->startAddress = blockAddr;
404 
405 uint64_t currentAddr = blockAddr;
406 bool blockEnded = false;
407 int zeroCount = 0;
408 
409 for (int i = 0; i < 1000 && !blockEnded; ++i) {
410 uint64_t fileOffset = 0;
411 if (!VirtualAddressToFileOffset(currentAddr, fileOffset))
412 break;
413 
414 if (fileOffset >= rawData_.size())
415 break;
416 
417 const uint8_t *code = rawData_.data() + fileOffset;
418 size_t code_size = rawData_.size() - fileOffset;
419 
420 if (code[0] == 0x00 && code[1] == 0x00) {
421 zeroCount++;
422 if (zeroCount > 8) {
423 blockEnded = true;
424 break;
425 }
426 } else {
427 zeroCount = 0;
428 }
429 
430 cs_insn *insn;
431 size_t count = cs_disasm(handle, code, 15, currentAddr, 1, &insn);
432 
433 if (count > 0) {
434 IR::Instruction instr;
435 instr.address = insn[0].address;
436 
437 instr.opcode = IR::OpCode::NOP;
438 switch (insn[0].id) {
439 case X86_INS_MOV:
440 instr.opcode = IR::OpCode::MOV;
441 break;
442 case X86_INS_MOVABS:
443 instr.opcode = IR::OpCode::MOV;
444 break;
445 case X86_INS_MOVAPS:
446 instr.opcode = IR::OpCode::MOV;
447 break;
448 case X86_INS_MOVUPS:
449 instr.opcode = IR::OpCode::MOV;
450 break;
451 case X86_INS_MOVDQA:
452 instr.opcode = IR::OpCode::MOV;
453 break;
454 case X86_INS_MOVDQU:
455 instr.opcode = IR::OpCode::MOV;
456 break;
457 case X86_INS_ADD:
458 instr.opcode = IR::OpCode::ADD;
459 break;
460 case X86_INS_SUB:
461 instr.opcode = IR::OpCode::SUB;
462 break;
463 case X86_INS_RET:
464 instr.opcode = IR::OpCode::RET;
465 break;
466 case X86_INS_CALL:
467 instr.opcode = IR::OpCode::CALL;
468 break;
469 case X86_INS_JMP:
470 instr.opcode = IR::OpCode::JMP;
471 break;
472 case X86_INS_JE:
473 case X86_INS_JCXZ:
474 case X86_INS_JECXZ:
475 case X86_INS_JRCXZ:
476 instr.opcode = IR::OpCode::JE;
477 break;
478 case X86_INS_JNE:
479 instr.opcode = IR::OpCode::JNE;
480 break;
481 case X86_INS_JG:
482 instr.opcode = IR::OpCode::JG;
483 break;
484 case X86_INS_JGE:
485 instr.opcode = IR::OpCode::JGE;
486 break;
487 case X86_INS_JL:
488 instr.opcode = IR::OpCode::JL;
489 break;
490 case X86_INS_JLE:
491 instr.opcode = IR::OpCode::JLE;
492 break;
493 case X86_INS_JA:
494 instr.opcode = IR::OpCode::JA;
495 break;
496 case X86_INS_JAE:
497 instr.opcode = IR::OpCode::JAE;
498 break;
499 case X86_INS_JB:
500 instr.opcode = IR::OpCode::JB;
501 break;
502 case X86_INS_JBE:
503 instr.opcode = IR::OpCode::JBE;
504 break;
505 case X86_INS_JS:
506 instr.opcode = IR::OpCode::JS;
507 break;
508 case X86_INS_JNS:
509 instr.opcode = IR::OpCode::JNS;
510 break;
511 case X86_INS_JO:
512 instr.opcode = IR::OpCode::JO;
513 break;
514 case X86_INS_JNO:
515 instr.opcode = IR::OpCode::JNO;
516 break;
517 case X86_INS_CMP:
518 instr.opcode = IR::OpCode::CMP;
519 break;
520 case X86_INS_LEA:
521 instr.opcode = IR::OpCode::LEA;
522 break;
523 
524 case X86_INS_MOVSX:
525 instr.opcode = IR::OpCode::MOVSX;
526 break;
527 case X86_INS_MOVSXD:
528 instr.opcode = IR::OpCode::MOVSX;
529 break;
530 case X86_INS_MOVZX:
531 instr.opcode = IR::OpCode::MOVZX;
532 break;
533 case X86_INS_MOVSD:
534 instr.opcode = IR::OpCode::MOV;
535 break; // Treat as MOV
536 case X86_INS_MOVSS:
537 instr.opcode = IR::OpCode::MOV;
538 break; // Treat as MOV
539 
540 case X86_INS_BSWAP:
541 instr.opcode = IR::OpCode::BSWAP;
542 break;
543 case X86_INS_FISTTP:
544 instr.opcode = IR::OpCode::FISTTP;
545 break;
546 case X86_INS_LEAVE:
547 instr.opcode = IR::OpCode::LEAVE;
548 break;
549 case X86_INS_INT:
550 instr.opcode = IR::OpCode::INT;
551 break; // Software interrupt, often for debug/syscall
552 
553 // AVX / VEX Support
554 case X86_INS_VMOVSS:
555 instr.opcode = IR::OpCode::MOV;
556 break;
557 case X86_INS_VMOVSD:
558 instr.opcode = IR::OpCode::MOV;
559 break;
560 case X86_INS_VMOVAPS:
561 instr.opcode = IR::OpCode::MOV;
562 break;
563 case X86_INS_VMOVUPS:
564 instr.opcode = IR::OpCode::MOV;
565 break;
566 case X86_INS_VMOVAPD:
567 instr.opcode = IR::OpCode::MOV;
568 break;
569 case X86_INS_VMOVUPD:
570 instr.opcode = IR::OpCode::MOV;
571 break;
572 case X86_INS_VADDSS:
573 instr.opcode = IR::OpCode::ADD;
574 break;
575 case X86_INS_VADDSD:
576 instr.opcode = IR::OpCode::ADD;
577 break;
578 case X86_INS_VSUBSS:
579 instr.opcode = IR::OpCode::SUB;
580 break;
581 case X86_INS_VSUBSD:
582 instr.opcode = IR::OpCode::SUB;
583 break;
584 case X86_INS_VMULSS:
585 instr.opcode = IR::OpCode::MUL;
586 break;
587 case X86_INS_VMULSD:
588 instr.opcode = IR::OpCode::MUL;
589 break;
590 case X86_INS_VDIVSS:
591 instr.opcode = IR::OpCode::DIV;
592 break;
593 case X86_INS_VDIVSD:
594 instr.opcode = IR::OpCode::DIV;
595 break;
596 case X86_INS_VXORPS:
597 instr.opcode = IR::OpCode::XOR;
598 break;
599 case X86_INS_VXORPD:
600 instr.opcode = IR::OpCode::XOR;
601 break;
602 case X86_INS_VANDPS:
603 instr.opcode = IR::OpCode::AND;
604 break;
605 case X86_INS_VANDPD:
606 instr.opcode = IR::OpCode::AND;
607 break;
608 case X86_INS_VORPS:
609 instr.opcode = IR::OpCode::OR;
610 break;
611 case X86_INS_VORPD:
612 instr.opcode = IR::OpCode::OR;
613 break;
614 case X86_INS_VUCOMISS:
615 instr.opcode = IR::OpCode::CMP;
616 break;
617 case X86_INS_VUCOMISD:
618 instr.opcode = IR::OpCode::CMP;
619 break;
620 case X86_INS_VPCMPGTB:
621 case X86_INS_VPCMPGTW:
622 case X86_INS_VPCMPGTD:
623 case X86_INS_VPCMPGTQ:
624 instr.opcode = IR::OpCode::CMP;
625 break;
626 
627 // Stack
628 case X86_INS_PUSH:
629 instr.opcode = IR::OpCode::PUSH;
630 break;
631 case X86_INS_POP:
632 instr.opcode = IR::OpCode::POP;
633 break;
634 
635 // Logic/Shift
636 case X86_INS_AND:
637 instr.opcode = IR::OpCode::AND;
638 break;
639 case X86_INS_OR:
640 instr.opcode = IR::OpCode::OR;
641 break;
642 case X86_INS_XOR:
643 instr.opcode = IR::OpCode::XOR;
644 break;
645 case X86_INS_SHL:
646 instr.opcode = IR::OpCode::SHL;
647 break;
648 case X86_INS_SHR:
649 instr.opcode = IR::OpCode::SHR;
650 break;
651 case X86_INS_SAL:
652 instr.opcode = IR::OpCode::SHL;
653 break;
654 case X86_INS_SAR:
655 instr.opcode = IR::OpCode::SHR;
656 break; // Treat arithmetic shift as logical for now or add SAR
657 case X86_INS_TEST:
658 instr.opcode = IR::OpCode::AND;
659 break; // TEST is mostly AND for flags
660 
661 // Arithmetic
662 case X86_INS_INC:
663 instr.opcode = IR::OpCode::ADD;
664 break; // Handled specially or mapped to ADD 1
665 case X86_INS_DEC:
666 instr.opcode = IR::OpCode::SUB;
667 break; // Handled specially or mapped to SUB 1
668 case X86_INS_NEG:
669 instr.opcode = IR::OpCode::SUB;
670 break; // 0 - x? Need proper unary op
671 case X86_INS_NOT:
672 instr.opcode = IR::OpCode::XOR;
673 break; // ~x (Need unary op really)
674 case X86_INS_MUL:
675 instr.opcode = IR::OpCode::MUL;
676 break;
677 case X86_INS_IMUL:
678 instr.opcode = IR::OpCode::MUL;
679 break;
680 case X86_INS_DIV:
681 instr.opcode = IR::OpCode::DIV;
682 break;
683 case X86_INS_IDIV:
684 instr.opcode = IR::OpCode::DIV;
685 break;
686 
687 default:
688 break;
689 }
690 instr.disassembly =
691 std::string(insn[0].mnemonic) + " " + insn[0].op_str;
692 
693 if (insn[0].detail) {
694 for (int j = 0; j < insn[0].detail->x86.op_count; ++j) {
695 const auto &op = insn[0].detail->x86.operands[j];
696 IR::Operand irOp;
697 if (op.type == X86_OP_REG) {
698 irOp.type = IR::Operand::Type::Register;
699 irOp.value = op.reg;
700 irOp.regName = cs_reg_name(handle, op.reg);
701 } else if (op.type == X86_OP_IMM) {
702 irOp.type = IR::Operand::Type::Immediate;
703 irOp.value = op.imm;
704 } else if (op.type == X86_OP_MEM) {
705 irOp.type = IR::Operand::Type::Memory;
706 // Store raw info for advanced lifting
707 irOp.memBase = op.mem.base;
708 irOp.memBaseName = (op.mem.base != X86_REG_INVALID)
709 ? cs_reg_name(handle, op.mem.base)
710 : "";
711 irOp.memDisp = op.mem.disp;
712 
713 if (op.mem.base == X86_REG_RIP) {
714 irOp.value = insn[0].address + insn[0].size + op.mem.disp;
715 irOp.name = "RIP";
716 } else if (op.mem.base == X86_REG_RSP) {
717 irOp.name = "RSP";
718 irOp.value = (uint64_t)op.mem.disp;
719 } else if (op.mem.base == X86_REG_RBP) {
720 irOp.name = "RBP";
721 irOp.value = (uint64_t)op.mem.disp;
722 } else {
723 irOp.value = 0;
724 irOp.name = "MEM";
725 }
726 } else {
727 irOp.type = IR::Operand::Type::Variable;
728 }
729 instr.operands.push_back(irOp);
730 }
731 }
732 
733 bb->instructions.push_back(instr);
734 
735 bool isBranch = false;
736 bool isCall = (insn[0].id == X86_INS_CALL);
737 bool isRet = (insn[0].id == X86_INS_RET);
738 
739 if (insn[0].id == X86_INS_JMP ||
740 (insn[0].id >= X86_INS_JA && insn[0].id <= X86_INS_JS)) {
741 isBranch = true;
742 }
743 
744 if (isCall) {
745 if (insn[0].detail->x86.op_count > 0 &&
746 insn[0].detail->x86.operands[0].type == X86_OP_IMM) {
747 uint64_t target = insn[0].detail->x86.operands[0].imm;
748 if (visitedGlobal.find(target) == visitedGlobal.end()) {
749 visitedGlobal.insert(target);
750 functionQueue.push(target);
751 }
752 }
753 } else if (isBranch) {
754 blockEnded = true;
755 if (insn[0].detail->x86.op_count > 0 &&
756 insn[0].detail->x86.operands[0].type == X86_OP_IMM) {
757 uint64_t target = insn[0].detail->x86.operands[0].imm;
758 bb->successors.push_back(target);
759 if (visitedBlocks.find(target) == visitedBlocks.end()) {
760 visitedBlocks.insert(target);
761 blockQueue.push(target);
762 }
763 }
764 if (insn[0].id != X86_INS_JMP) {
765 uint64_t nextAddr = insn[0].address + insn[0].size;
766 bb->successors.push_back(nextAddr);
767 if (visitedBlocks.find(nextAddr) == visitedBlocks.end()) {
768 visitedBlocks.insert(nextAddr);
769 blockQueue.push(nextAddr);
770 }
771 }
772 } else if (isRet) {
773 blockEnded = true;
774 }
775 
776 currentAddr += insn[0].size;
777 cs_free(insn, count);
778 } else {
779 blockEnded = true;
780 }
781 }
782 bb->endAddress = currentAddr;
783 func->basicBlocks.push_back(bb);
784 }
785 
786 cs_close(&handle);
787 functions_.push_back(func);
788 functionsAnalyzed++;
789 progress_.functionsAnalyzed = functionsAnalyzed;
790 progress_.currentPhase = "analyzing";
791 }
792 
793 LOG_INFO(Common, "Discovered {} functions.", functions_.size());
794 progress_.currentPhase = "complete";
795 progress_.isComplete = true;
796 isAnalyzed_ = true;
797}
798 
799void DecompilerContext::AnalyzeFunction(uint64_t startAddress,
800 std::set<uint64_t> &visitedGlobal) {}
801 
802void DecompilerContext::ExportProject(const std::string &outPath) {
803 std::filesystem::path root(outPath);
804 std::filesystem::create_directories(root / "include");
805 std::filesystem::create_directories(root / "src");
806 
807 LOG_INFO(Common, "Starting full project analysis and export...");
808 
809 auto symbols = std::make_shared<Analysis::SymbolAnalysis>(
810 rawData_, baseAddress_, symbolDatabase_);
811 symbols->analyze();
812 
813 std::vector<std::shared_ptr<AST::FunctionAST>> analyzedASTs;
814 analyzedASTs.reserve(functions_.size());
815 
816 // ┌─────────────────────────────────────────────────────────────────────────┐
817 // │ PASS 1: Full Analysis with progress output and timeout protection │
818 // └─────────────────────────────────────────────────────────────────────────┘
819 size_t skippedCount = 0;
820 auto globalStart = std::chrono::steady_clock::now();
821 
822 for (size_t i = 0; i < functions_.size(); ++i) {
823 auto func = functions_[i];
824 
825 // Progress output every 100 functions
826 if (i % 100 == 0 || i == functions_.size() - 1) {
827 auto elapsed = std::chrono::steady_clock::now() - globalStart;
828 auto secs =
829 std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
830 std::cout << "\r[" << (i + 1) << "/" << functions_.size() << "] "
831 << "Analyzing... (" << secs << "s, " << skippedCount
832 << " skipped)" << std::flush;
833 }
834 
835 size_t funcSize = 0;
836 for (const auto &bb : func->basicBlocks) {
837 if (!bb->instructions.empty())
838 funcSize += (bb->endAddress - bb->startAddress);
839 }
840 
841 // Skip very large functions (likely data, not code)
842 if (funcSize > 100000) {
843 LOG_INFO(Common, "Skipping huge function {} ({}KB)", func->name,
844 funcSize / 1024);
845 skippedCount++;
846 continue;
847 }
848 
849 auto funcStart = std::chrono::steady_clock::now();
850 
851 try {
852 auto dom = std::make_shared<Analysis::DominatorAnalysis>();
853 dom->analyze(func);
854 
855 // Timeout check after dominator analysis
856 auto elapsed = std::chrono::steady_clock::now() - funcStart;
857 if (elapsed > std::chrono::seconds(5)) {
858 LOG_INFO(Common, "Skipping {} (dominator timeout)", func->name);
859 skippedCount++;
860 continue;
861 }
862 
863 // 3. Variable Lifting
864 auto lifter = std::make_shared<Lifter::VariableAnalysis>(func);
865 lifter->analyze();
866 
867 // 4. Structural Analysis
868 Analysis::StructuralAnalysis structural(func, dom, symbols, lifter);
869 auto ast = structural.analyze();
870 
871 // Timeout check after structural analysis
872 elapsed = std::chrono::steady_clock::now() - funcStart;
873 if (elapsed > std::chrono::seconds(10)) {
874 LOG_INFO(Common, "Skipping {} (structural timeout)", func->name);
875 skippedCount++;
876 continue;
877 }
878 
879 if (!ast->body || ast->body->statements.empty()) {
880 // Empty function - still add stub
881 }
882 
883 // Apply locals to AST
884 lifter->applyToAST(ast);
885 
886 // 4b. Apply User Types
887 for (auto &local : ast->locals) {
888 auto userType = GetUserVarType(func->address, local.stackOffset);
889 if (userType) {
890 local.complexType = userType;
891 }
892 }
893 
894 Analysis::DataFlowAnalysis dataflow(ast);
895 dataflow.analyze();
896 
897 Analysis::MemberAccessAnalysis memberAccess(typeManager_);
898 memberAccess.analyze(ast);
899 
900 analyzedASTs.push_back(ast);
901 SetFunctionParamCount(func->address, (int)ast->parameters.size());
902 
903 } catch (const std::exception &e) {
904 LOG_ERROR(Common, "Exception analyzing {}: {}", func->name, e.what());
905 skippedCount++;
906 continue;
907 }
908 }
909 
910 std::cout << std::endl; // Newline after progress
911 LOG_INFO(Common, "Analysis complete: {} functions OK, {} skipped",
912 analyzedASTs.size(), skippedCount);
913 
914 // --- PASS 2: Export Types ---
915 {
916 std::ofstream hTypes(root / "include" / "types.h");
917 hTypes << "#pragma once\n";
918 hTypes << "#include <cstdint>\n\n";
919 hTypes << "#if defined(__GNUC__) || defined(__clang__)\n";
920 hTypes << "#define _byteswap_uint64 __builtin_bswap64\n";
921 hTypes << "#endif\n\n";
922 hTypes << "static inline double as_double(uint64_t i) { return "
923 "*(double*)&i; }\n";
924 hTypes
925 << "static inline float as_float(uint32_t i) { return *(float*)&i; }\n";
926 hTypes << "static inline uint64_t as_uint64(double d) { return "
927 "*(uint64_t*)&d; }\n";
928 hTypes << "static inline uint32_t as_uint32(float f) { return "
929 "*(uint32_t*)&f; }\n\n";
930 
931 for (const auto &[name, type] : typeManager_->getAllTypes()) {
932 if (type->getKind() == Analysis::Type::Kind::Struct) {
933 hTypes << "struct " << name << ";\n";
934 }
935 }
936 hTypes << "\n";
937 
938 for (const auto &[name, type] : typeManager_->getAllTypes()) {
939 if (type->getKind() == Analysis::Type::Kind::Struct) {
940 auto structType = std::dynamic_pointer_cast<Analysis::StructType>(type);
941 hTypes << "struct " << name << " {\n";
942 for (const auto &member : structType->getMembers()) {
943 hTypes << " " << member.type->toString() << " " << member.name
944 << "; // Offset: " << member.offset << "\n";
945 }
946 hTypes << "};\n\n";
947 }
948 }
949 }
950 
951 // --- PASS 3: Export Functions ---
952 {
953 std::map<std::string, std::shared_ptr<AST::FunctionAST>> nameToAst;
954 for (auto &ast : analyzedASTs) {
955 nameToAst[ast->name] = ast;
956 }
957 
958 std::ofstream hFuncs(root / "include" / "functions.h");
959 hFuncs << "#pragma once\n";
960 hFuncs << "#include \"types.h\"\n";
961 hFuncs << "#include <cstdint>\n\n";
962 
963 // All functions use uniform int64_t(a1..a6) signature to match CppEmitter output.
964 // Default args allow call sites with fewer arguments to compile cleanly.
965 static const char* kFuncSig =
966 "(int64_t a1 = 0, int64_t a2 = 0, int64_t a3 = 0, "
967 "int64_t a4 = 0, int64_t a5 = 0, int64_t a6 = 0);\n";
968 for (const auto &func : functions_) {
969 hFuncs << "int64_t " << func->name << kFuncSig;
970 }
971 }
972 
973 // --- PASS 4: Export Source ---
974 std::cout << "Exporting source code..." << std::endl;
975 LOG_INFO(Common, "Starting source export of {} functions",
976 analyzedASTs.size());
977 
978 int fileCounter = 0;
979 int funcsPerFile = 50;
980 
981 for (size_t i = 0; i < analyzedASTs.size(); i += funcsPerFile) {
982 if (i % 1000 == 0) {
983 std::cout << "\rExporting chunks... " << i << "/" << analyzedASTs.size()
984 << std::flush;
985 }
986 
987 std::string filename = "segment_" + std::to_string(fileCounter++) + ".cpp";
988 std::ofstream cppFile(root / "src" / filename);
989 
990 cppFile << "#include \"../include/functions.h\"\n";
991 cppFile << "#include \"../include/types.h\"\n";
992 cppFile << "#include \"../include/globals.h\"\n\n";
993 
994 for (size_t j = i; j < i + funcsPerFile && j < analyzedASTs.size(); ++j) {
995 auto ast = analyzedASTs[j];
996 try {
997 Codegen::CppEmitter emitter;
998 cppFile << emitter.generate(ast) << "\n\n";
999 } catch (const std::exception &e) {
1000 LOG_ERROR(Common, "Failed to generate code for {}: {}", ast->name,
1001 e.what());
1002 cppFile << "// Error generating " << ast->name << ": " << e.what()
1003 << "\n\n";
1004 } catch (...) {
1005 LOG_ERROR(Common, "Unknown failure generating code for {}", ast->name);
1006 cppFile << "// Unknown error generating " << ast->name << "\n\n";
1007 }
1008 }
1009 }
1010 std::cout << std::endl;
1011 
1012 // --- PASS 5: Export Globals ---
1013 {
1014 std::ofstream hGlobals(root / "include" / "globals.h");
1015 std::ofstream cppGlobals(root / "src" / "globals.cpp");
1016 
1017 hGlobals << "#pragma once\n";
1018 hGlobals << "#include \"types.h\"\n";
1019 hGlobals << "#include <cstdint>\n";
1020 hGlobals << "#include \"ps4_stubs.h\"\n\n";
1021 // g_ps4_memory is the flat PS4 global address space, backed by ps4MEL.
1022 // Initialized in main() after runtime_init().
1023 hGlobals << "// PS4 flat memory space - index with PS4 virtual address\n";
1024 hGlobals << "extern uint8_t* g_ps4_memory;\n\n";
1025 hGlobals << "// Translate a value that may be a PS4 virtual address or a host pointer\n";
1026 hGlobals << "// into a safe host pointer for dereference.\n";
1027 hGlobals << "// - Small values (<= 16MB) are PS4 virtual addresses -> map into g_ps4_memory\n";
1028 hGlobals << "// - Values inside the g_ps4_memory allocation are used directly\n";
1029 hGlobals << "// - Other values (nullptr or huge) fall back to g_ps4_memory base\n";
1030 hGlobals << "inline void* ps4_resolve(int64_t addr) {\n";
1031 hGlobals << " if (!g_ps4_memory) return (void*)g_ps4_memory;\n";
1032 hGlobals << " const int64_t base = (int64_t)g_ps4_memory;\n";
1033 hGlobals << " // Already a host pointer inside g_ps4_memory\n";
1034 hGlobals << " if (addr >= base && addr < base + 0x1000000LL) return (void*)addr;\n";
1035 hGlobals << " // PS4 virtual address: map into emulated memory (clamp to valid range)\n";
1036 hGlobals << " uint64_t va = (uint64_t)addr;\n";
1037 hGlobals << " if (va >= 0x1000000ULL) va = va % 0x1000000ULL;\n";
1038 hGlobals << " return (void*)&g_ps4_memory[va];\n";
1039 hGlobals << "}\n\n";
1040 
1041 cppGlobals << "#include \"../include/globals.h\"\n\n";
1042 cppGlobals << "// Initialized in main() via PS4Emu::GetGlobalMemoryBase()\n";
1043 cppGlobals << "uint8_t* g_ps4_memory = nullptr;\n\n";
1044 
1045 // We need to iterate the symbol database for globals
1046 if (symbolDatabase_) {
1047 const auto &syms = symbolDatabase_->getSymbols();
1048 for (const auto &[addr, sym] : syms) {
1049 if (sym.type == Analysis::SymbolType::GlobalVariable) {
1050 // Parse type from name: g_TYPE_ADDR
1051 std::string typeName = "int64_t";
1052 
1053 size_t firstUnderscore = sym.name.find('_');
1054 size_t lastUnderscore = sym.name.rfind('_');
1055 
1056 if (firstUnderscore != std::string::npos &&
1057 lastUnderscore != std::string::npos &&
1058 lastUnderscore > firstUnderscore) {
1059 typeName = sym.name.substr(firstUnderscore + 1,
1060 lastUnderscore - (firstUnderscore + 1));
1061 }
1062 
1063 // Read initial value from memory
1064 std::string initVal = "0";
1065 uint64_t offset = addr - baseAddress_;
1066 
1067 if (offset < rawData_.size()) {
1068 std::stringstream ss;
1069 ss << "0x" << std::hex;
1070 
1071 if (typeName.find("int8") != std::string::npos ||
1072 typeName == "char" || typeName == "bool") {
1073 uint16_t val =
1074 rawData_[offset]; // Use uint16 for stream formatting of uint8
1075 ss << val;
1076 initVal = ss.str();
1077 } else if (typeName.find("int16") != std::string::npos ||
1078 typeName == "short") {
1079 if (offset + 2 <= rawData_.size()) {
1080 uint16_t val =
1081 *reinterpret_cast<const uint16_t *>(&rawData_[offset]);
1082 ss << val;
1083 initVal = ss.str();
1084 }
1085 } else if (typeName.find("int32") != std::string::npos ||
1086 typeName == "int" || typeName == "float") {
1087 if (offset + 4 <= rawData_.size()) {
1088 uint32_t val =
1089 *reinterpret_cast<const uint32_t *>(&rawData_[offset]);
1090 ss << val;
1091 initVal = ss.str();
1092 }
1093 if (typeName == "float")
1094 initVal = "0.0f"; // Placeholder
1095 } else { // int64, long, double, pointer
1096 if (offset + 8 <= rawData_.size()) {
1097 uint64_t val =
1098 *reinterpret_cast<const uint64_t *>(&rawData_[offset]);
1099 ss << val;
1100 initVal = ss.str();
1101 }
1102 if (typeName == "double")
1103 initVal = "0.0"; // Placeholder
1104 if (typeName == "float")
1105 initVal = "0.0f";
1106 }
1107 } else if (typeName == "double") {
1108 initVal = "0.0";
1109 } else if (typeName == "float") {
1110 initVal = "0.0f";
1111 } else {
1112 initVal = "{}"; // Aggregate init/default
1113 }
1114 
1115 hGlobals << "extern " << typeName << " " << sym.name << "; // 0x"
1116 << std::hex << addr << "\n";
1117 cppGlobals << typeName << " " << sym.name << " = " << initVal
1118 << ";\n";
1119 }
1120 }
1121 }
1122 }
1123 
1124 // --- PASS 6: Copy ps4MEL Runtime Files ---
1125 {
1126 std::filesystem::create_directories(root / "ps4mel");
1127
1128 // List of ps4MEL files to copy - all headers and implementations
1129 std::vector<std::string> ps4melFiles = {
1130 "ps4_memory.cpp", "ps4_memory.h",
1131 "ps4_kernel.cpp", "ps4_kernel.h",
1132 "ps4_pthread.cpp", "ps4_pthread.h",
1133 "ps4_tls.cpp", "ps4_tls.h",
1134 "ps4_stubs.cpp", "ps4_stubs.h",
1135 "ps4_safe_memory.h",
1136 "sdl_backend.cpp", "sdl_backend.h",
1137 "gnm_driver.h",
1138 "ps4_graphics.h",
1139 "runtime.cpp", "runtime.h"
1140 };
1141
1142 // Try to find ps4MEL directory relative to executable or source
1143 std::filesystem::path ps4melSrc;
1144 std::vector<std::filesystem::path> searchPaths = {
1145 std::filesystem::current_path() / "ps4MEL",
1146 std::filesystem::current_path().parent_path() / "ps4MEL",
1147 std::filesystem::path(__FILE__).parent_path().parent_path().parent_path() / "ps4MEL"
1148 };
1149
1150 for (const auto& p : searchPaths) {
1151 if (std::filesystem::exists(p)) {
1152 ps4melSrc = p;
1153 break;
1154 }
1155 }
1156
1157 if (!ps4melSrc.empty()) {
1158 LOG_INFO(Common, "Copying ps4MEL files from: {}", ps4melSrc.string());
1159 for (const auto& file : ps4melFiles) {
1160 std::filesystem::path srcFile = ps4melSrc / file;
1161 std::filesystem::path dstFile = root / "ps4mel" / file;
1162 if (std::filesystem::exists(srcFile)) {
1163 try {
1164 std::filesystem::copy_file(srcFile, dstFile,
1165 std::filesystem::copy_options::overwrite_existing);
1166 } catch (...) {
1167 LOG_WARNING(Common, "Failed to copy: {}", file);
1168 }
1169 }
1170 }
1171 } else {
1172 LOG_WARNING(Common, "ps4MEL directory not found, generating minimal stubs");
1173 }
1174 }
1175 
1176 // --- PASS 7: Export Stubs, Runtime and Main ---
1177 {
1178 std::filesystem::create_directories(root / "assets");
1179 
1180 std::map<std::string, std::shared_ptr<AST::FunctionAST>> nameToAst;
1181 for (auto &ast : analyzedASTs) {
1182 nameToAst[ast->name] = ast;
1183 }
1184 
1185 // 1. Export Stubs
1186 {
1187 std::ofstream stubs(root / "src" / "stubs.cpp");
1188 stubs << "#include \"../include/functions.h\"\n\n";
1189 for (const auto &func : functions_) {
1190 if (nameToAst.find(func->name) == nameToAst.end()) {
1191 stubs << "int64_t " << func->name
1192 << "(int64_t a1, int64_t a2, int64_t a3, "
1193 "int64_t a4, int64_t a5, int64_t a6) { return 0; }\n";
1194 }
1195 }
1196 }
1197 
1198 // 4. Export Main (using ps4MEL runtime)
1199 {
1200 // Find entry point name
1201 std::string entryName = "";
1202 {
1203 std::stringstream ss;
1204 ss << "sub_" << std::hex << entryPoint_;
1205 entryName = ss.str();
1206 }
1207 auto it = std::find_if(functions_.begin(), functions_.end(),
1208 [this](auto f) { return f->address == entryPoint_; });
1209 if (it != functions_.end()) {
1210 entryName = (*it)->name;
1211 }
1212 
1213 std::ofstream mainCpp(root / "src" / "main.cpp");
1214 mainCpp << "/*\n"
1215 << " * Decompiled Game - Main Entry Point\n"
1216 << " * Generated by ShadPKG Decompiler\n"
1217 << " * Entry point: " << entryName
1218 << " @ 0x" << std::hex << entryPoint_ << std::dec << "\n"
1219 << " */\n\n";
1220 mainCpp << "#include \"../include/functions.h\"\n";
1221 mainCpp << "#include \"../include/globals.h\"\n";
1222 mainCpp << "#include \"../ps4mel/runtime.h\"\n";
1223 mainCpp << "#include \"../ps4mel/ps4_memory.h\"\n";
1224 mainCpp << "#include <cstdio>\n";
1225 mainCpp << "#include <cstring>\n";
1226 mainCpp << "#include <vector>\n";
1227 mainCpp << "#include <iostream>\n";
1228 mainCpp << "#include <csignal>\n";
1229 mainCpp << "#include <cstdlib>\n\n";
1230 
1231 mainCpp << "// ─── Signal handler ───────────────────────────────────────\n";
1232 mainCpp << "static volatile bool g_running = true;\n";
1233 mainCpp << "static void signal_handler(int sig) {\n";
1234 mainCpp << " (void)sig;\n";
1235 mainCpp << " std::cout << \"\\n[RUNTIME] Signal caught, shutting down...\" << std::endl;\n";
1236 mainCpp << " g_running = false;\n";
1237 mainCpp << " std::exit(0);\n";
1238 mainCpp << "}\n\n";
1239 
1240 mainCpp << "// ─── Load ELF segments from memory.bin ─────────────────────\n";
1241 mainCpp << "static void load_memory_bin() {\n";
1242 mainCpp << " FILE* f = std::fopen(\"data/memory.bin\", \"rb\");\n";
1243 mainCpp << " if (!f) { std::cout << \"[RUNTIME] No data/memory.bin found, skipping.\" << std::endl; return; }\n";
1244 mainCpp << " uint32_t magic = 0, num_segs = 0;\n";
1245 mainCpp << " std::fread(&magic, 4, 1, f);\n";
1246 mainCpp << " if (magic != 0x34345350u) { std::fclose(f); return; } // 'PS44'\n";
1247 mainCpp << " std::fread(&num_segs, 4, 1, f);\n";
1248 mainCpp << " for (uint32_t i = 0; i < num_segs; i++) {\n";
1249 mainCpp << " uint64_t vaddr = 0, size = 0;\n";
1250 mainCpp << " std::fread(&vaddr, 8, 1, f);\n";
1251 mainCpp << " std::fread(&size, 8, 1, f);\n";
1252 mainCpp << " if (size == 0) continue;\n";
1253 mainCpp << " // Translate virtual address to offset in g_ps4_memory\n";
1254 mainCpp << " void* dst = PS4Emu::TranslateAddress(vaddr);\n";
1255 mainCpp << " if (dst) std::fread(dst, 1, size, f);\n";
1256 mainCpp << " else { std::vector<uint8_t> skip(size); std::fread(skip.data(), 1, size, f); }\n";
1257 mainCpp << " }\n";
1258 mainCpp << " std::fclose(f);\n";
1259 mainCpp << " std::cout << \"[RUNTIME] ELF segments loaded from data/memory.bin\" << std::endl;\n";
1260 mainCpp << "}\n\n";
1261 
1262 mainCpp << "int main(int argc, char* argv[]) {\n";
1263 mainCpp << " (void)argc; (void)argv;\n";
1264 mainCpp << " std::signal(SIGINT, signal_handler);\n";
1265 mainCpp << " std::signal(SIGTERM, signal_handler);\n\n";
1266 mainCpp << " std::cout << \"[RUNTIME] ShadPKG Decompiled Runtime starting...\" << std::endl;\n\n";
1267 mainCpp << " // 1. Init ps4MEL (memory, TLS, VFS)\n";
1268 mainCpp << " runtime_init();\n\n";
1269 mainCpp << " // 2. Point g_ps4_memory at ps4MEL's allocated block\n";
1270 mainCpp << " g_ps4_memory = static_cast<uint8_t*>(PS4Emu::GetGlobalMemoryBase());\n";
1271 mainCpp << " if (!g_ps4_memory) {\n";
1272 mainCpp << " std::cerr << \"[RUNTIME] FATAL: ps4MEL memory not initialized\" << std::endl;\n";
1273 mainCpp << " return 1;\n";
1274 mainCpp << " }\n\n";
1275 mainCpp << " // 3. Load ELF segment data into g_ps4_memory\n";
1276 mainCpp << " load_memory_bin();\n\n";
1277
1278 // Add .init_array execution if present
1279 if (initArrayAddr_ != 0 && initArraySize_ > 0) {
1280 mainCpp << " // 4. Execute .init_array constructors\n";
1281 mainCpp << " {\n";
1282 mainCpp << " typedef int64_t (*init_func_t)(void);\n";
1283 mainCpp << " uint64_t init_array_va = 0x" << std::hex << initArrayAddr_ << std::dec << ";\n";
1284 mainCpp << " uint64_t init_array_size = 0x" << std::hex << initArraySize_ << std::dec << ";\n";
1285 mainCpp << " uint64_t num_funcs = init_array_size / sizeof(uint64_t);\n";
1286 mainCpp << " std::cout << \"[RUNTIME] Executing \" << num_funcs << \" .init_array functions...\" << std::endl;\n";
1287 mainCpp << " for (uint64_t i = 0; i < num_funcs; i++) {\n";
1288 mainCpp << " uint64_t func_ptr_addr = init_array_va + (i * sizeof(uint64_t));\n";
1289 mainCpp << " uint64_t func_ptr = *((uint64_t*)(g_ps4_memory + func_ptr_addr));\n";
1290 mainCpp << " if (func_ptr != 0) {\n";
1291 mainCpp << " init_func_t init_func = reinterpret_cast<init_func_t>(g_ps4_memory + func_ptr);\n";
1292 mainCpp << " try {\n";
1293 mainCpp << " init_func();\n";
1294 mainCpp << " } catch (...) {\n";
1295 mainCpp << " std::cerr << \"[RUNTIME] .init_array[\" << i << \"] threw exception\" << std::endl;\n";
1296 mainCpp << " }\n";
1297 mainCpp << " }\n";
1298 mainCpp << " }\n";
1299 mainCpp << " }\n\n";
1300 }
1301
1302 mainCpp << " std::cout << \"[RUNTIME] Entry: " << entryName << " @ 0x"
1303 << std::hex << entryPoint_ << std::dec << "\" << std::endl;\n\n";
1304 mainCpp << " // 4. Run the decompiled entry point\n";
1305 mainCpp << " try {\n";
1306 mainCpp << " int64_t result = " << entryName << "();\n";
1307 mainCpp << " std::cout << \"[RUNTIME] Entry returned: \" << result << std::endl;\n";
1308 mainCpp << " } catch (const std::exception& e) {\n";
1309 mainCpp << " std::cerr << \"[RUNTIME] Exception: \" << e.what() << std::endl;\n";
1310 mainCpp << " } catch (...) {\n";
1311 mainCpp << " std::cerr << \"[RUNTIME] Unknown exception\" << std::endl;\n";
1312 mainCpp << " }\n\n";
1313 mainCpp << " runtime_shutdown();\n";
1314 mainCpp << " std::cout << \"[RUNTIME] Shutdown complete. Frames: \"\n";
1315 mainCpp << " << PS4Emu::GetFrameCounter() << std::endl;\n";
1316 mainCpp << " return 0;\n";
1317 mainCpp << "}\n";
1318 }
1319 }
1320 
1321 // --- PASS 8: Export memory.bin (ELF segments for runtime loading) ---
1322 {
1323 std::filesystem::create_directories(root / "data");
1324 std::ofstream memFile(root / "data" / "memory.bin", std::ios::binary);
1325 
1326 uint32_t magic = 0x34345350u; // 'PS44'
1327 uint32_t numSegs = static_cast<uint32_t>(segments_.size());
1328 memFile.write(reinterpret_cast<const char*>(&magic), 4);
1329 memFile.write(reinterpret_cast<const char*>(&numSegs), 4);
1330 
1331 for (const auto& seg : segments_) {
1332 uint64_t vaddr = seg.virtualAddress;
1333 uint64_t memSize = seg.memSize > 0 ? seg.memSize : seg.size; // Use memSize for BSS padding
1334 memFile.write(reinterpret_cast<const char*>(&vaddr), 8);
1335 memFile.write(reinterpret_cast<const char*>(&memSize), 8);
1336
1337 // Write file data (p_filesz bytes)
1338 if (seg.size > 0 && seg.fileOffset + seg.size <= rawData_.size()) {
1339 memFile.write(reinterpret_cast<const char*>(rawData_.data() + seg.fileOffset),
1340 static_cast<std::streamsize>(seg.size));
1341 } else if (seg.size > 0) {
1342 // Partial write + zero-pad
1343 uint64_t available = rawData_.size() > seg.fileOffset ? rawData_.size() - seg.fileOffset : 0;
1344 if (available > 0) {
1345 memFile.write(reinterpret_cast<const char*>(rawData_.data() + seg.fileOffset),
1346 static_cast<std::streamsize>(available));
1347 }
1348 std::vector<char> zeros(seg.size - available, 0);
1349 memFile.write(zeros.data(), static_cast<std::streamsize>(zeros.size()));
1350 }
1351
1352 // Write BSS zero-padding (memSize - size bytes)
1353 if (memSize > seg.size) {
1354 std::vector<char> zeros(memSize - seg.size, 0);
1355 memFile.write(zeros.data(), static_cast<std::streamsize>(zeros.size()));
1356 }
1357 }
1358 LOG_INFO(Common, "Exported {} ELF segments to data/memory.bin", numSegs);
1359 }
1360 
1361 {
1362 std::ofstream cmake(root / "CMakeLists.txt");
1363 cmake << "cmake_minimum_required(VERSION 3.14)\n";
1364 cmake << "project(DecompiledGame CXX)\n\n";
1365 cmake << "set(CMAKE_CXX_STANDARD 17)\n";
1366 cmake << "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n";
1367 cmake << "# Compiler flags for large generated files\n";
1368 cmake << "add_compile_options(\n";
1369 cmake << " -fbracket-depth=1024\n";
1370 cmake << " -Wno-unused-variable\n";
1371 cmake << " -Wno-unused-but-set-variable\n";
1372 cmake << " -Wno-unused-parameter\n";
1373 cmake << " -Wno-missing-field-initializers\n";
1374 cmake << " -Wno-tautological-compare\n";
1375 cmake << " -Wno-tautological-constant-out-of-range-compare\n";
1376 cmake << " -Wno-logical-not-parentheses\n";
1377 cmake << " -O0\n";
1378 cmake << ")\n\n";
1379 cmake << "# Include directories\n";
1380 cmake << "include_directories(include)\n";
1381 cmake << "include_directories(ps4mel)\n\n";
1382 cmake << "# SDL2 - required by ps4MEL graphics/stub layer\n";
1383 cmake << "find_package(SDL2 QUIET)\n";
1384 cmake << "if(SDL2_FOUND)\n";
1385 cmake << " message(STATUS \"SDL2 found: ${SDL2_INCLUDE_DIRS}\")\n";
1386 cmake << " include_directories(${SDL2_INCLUDE_DIRS})\n";
1387 cmake << " set(HAS_SDL2 TRUE)\n";
1388 cmake << "else()\n";
1389 cmake << " # Try pkg-config fallback\n";
1390 cmake << " find_package(PkgConfig QUIET)\n";
1391 cmake << " if(PKG_CONFIG_FOUND)\n";
1392 cmake << " pkg_check_modules(SDL2 QUIET sdl2)\n";
1393 cmake << " if(SDL2_FOUND)\n";
1394 cmake << " include_directories(${SDL2_INCLUDE_DIRS})\n";
1395 cmake << " set(HAS_SDL2 TRUE)\n";
1396 cmake << " endif()\n";
1397 cmake << " endif()\n";
1398 cmake << " if(NOT HAS_SDL2)\n";
1399 cmake << " message(WARNING \"SDL2 not found. Graphics stubs will be disabled.\")\n";
1400 cmake << " add_definitions(-DPS4_NO_SDL)\n";
1401 cmake << " endif()\n";
1402 cmake << "endif()\n\n";
1403 cmake << "# Game source files\n";
1404 cmake << "file(GLOB GAME_SOURCES \"src/*.cpp\")\n\n";
1405 cmake << "# PS4 Runtime Emulation Layer (ps4MEL)\n";
1406 cmake << "set(PS4MEL_SOURCES\n";
1407 cmake << " ps4mel/ps4_memory.cpp\n";
1408 cmake << " ps4mel/ps4_kernel.cpp\n";
1409 cmake << " ps4mel/ps4_pthread.cpp\n";
1410 cmake << " ps4mel/ps4_tls.cpp\n";
1411 cmake << " ps4mel/ps4_stubs.cpp\n";
1412 cmake << " ps4mel/runtime.cpp\n";
1413 cmake << ")\n";
1414 cmake << "if(HAS_SDL2)\n";
1415 cmake << " list(APPEND PS4MEL_SOURCES ps4mel/sdl_backend.cpp)\n";
1416 cmake << "endif()\n\n";
1417 cmake << "add_executable(Game ${GAME_SOURCES} ${PS4MEL_SOURCES})\n\n";
1418 cmake << "# Platform-specific linking\n";
1419 cmake << "if(APPLE)\n";
1420 cmake << " target_link_libraries(Game PRIVATE pthread)\n";
1421 cmake << " if(HAS_SDL2)\n";
1422 cmake << " target_link_libraries(Game PRIVATE ${SDL2_LIBRARIES})\n";
1423 cmake << " endif()\n";
1424 cmake << "elseif(UNIX)\n";
1425 cmake << " target_link_libraries(Game PRIVATE pthread dl)\n";
1426 cmake << " if(HAS_SDL2)\n";
1427 cmake << " target_link_libraries(Game PRIVATE ${SDL2_LIBRARIES})\n";
1428 cmake << " endif()\n";
1429 cmake << "elseif(WIN32)\n";
1430 cmake << " if(HAS_SDL2)\n";
1431 cmake << " target_link_libraries(Game PRIVATE ${SDL2_LIBRARIES})\n";
1432 cmake << " endif()\n";
1433 cmake << "endif()\n\n";
1434 cmake << "# Copy data folder next to binary\n";
1435 cmake << "add_custom_command(TARGET Game POST_BUILD\n";
1436 cmake << " COMMAND ${CMAKE_COMMAND} -E copy_directory\n";
1437 cmake << " ${CMAKE_SOURCE_DIR}/data $<TARGET_FILE_DIR:Game>/data\n";
1438 cmake << " COMMENT \"Copying data/memory.bin to build directory\"\n";
1439 cmake << ")\n";
1440 }
1441 
1442 std::cout << "Project exported to: " << outPath << "\n";
1443}
1444 
1445std::string DecompilerContext::GenerateCode() {
1446 if (!isAnalyzed_)
1447 return "// Not analyzed yet";
1448 
1449 std::stringstream ss;
1450 ss << "// Decompiled by ShadPKG\n";
1451 ss << "// Architecture: x86-64\n\n";
1452 
1453 for (const auto &func : functions_) {
1454 ss << func->signature << " {\n";
1455 for (const auto &bb : func->basicBlocks) {
1456 ss << " // Block: 0x" << std::hex << bb->startAddress << "\n";
1457 for (const auto &instr : bb->instructions) {
1458 ss << " " << instr.disassembly << ";\n";
1459 }
1460 if (!bb->successors.empty()) {
1461 ss << " goto ";
1462 for (auto succ : bb->successors)
1463 ss << "0x" << std::hex << succ << " ";
1464 ss << ";\n";
1465 }
1466 ss << "\n";
1467 }
1468 ss << "}\n\n";
1469 }
1470 
1471 return ss.str();
1472}
1473 
1474std::string DecompilerContext::GenerateStructuredCode() {
1475 if (!isAnalyzed_)
1476 return "// Not analyzed yet (Run Analysis first)";
1477 
1478 std::stringstream ss;
1479 ss << "// Decompiled by ShadPKG (Structured Analysis)\n";
1480 ss << "// Architecture: x86-64\n\n";
1481 
1482 auto symbols = std::make_shared<Analysis::SymbolAnalysis>(
1483 rawData_, baseAddress_, symbolDatabase_);
1484 symbols->analyze();
1485 
1486 for (const auto &func : functions_) {
1487 auto dom = std::make_shared<Analysis::DominatorAnalysis>();
1488 dom->analyze(func);
1489 
1490 auto lifter = std::make_shared<Lifter::VariableAnalysis>(func);
1491 lifter->analyze();
1492 
1493 Analysis::StructuralAnalysis structural(func, dom, symbols, lifter);
1494 auto ast = structural.analyze();
1495 
1496 lifter->applyToAST(ast);
1497 
1498 for (auto &local : ast->locals) {
1499 auto userType = GetUserVarType(func->address, local.stackOffset);
1500 if (userType) {
1501 local.complexType = userType;
1502 }
1503 }
1504 
1505 Analysis::DataFlowAnalysis dataflow(ast);
1506 dataflow.analyze();
1507 
1508 Analysis::MemberAccessAnalysis memberAccess(typeManager_);
1509 memberAccess.analyze(ast);
1510 
1511 Codegen::CppEmitter emitter;
1512 ss << emitter.generate(ast);
1513 functionTokens_[func->address] = emitter.getTokens();
1514 ss << "\n";
1515 }
1516 
1517 return ss.str();
1518}
1519 
1520const std::vector<Codegen::CppEmitter::Token> &
1521DecompilerContext::GetFunctionTokens(uint64_t address) const {
1522 if (functionTokens_.count(address)) {
1523 return functionTokens_.at(address);
1524 }
1525 static std::vector<Codegen::CppEmitter::Token> empty;
1526 return empty;
1527}
1528 
1529uint64_t DecompilerContext::ReadPointer(uint64_t address) const {
1530 if (address < baseAddress_)
1531 return 0;
1532 uint64_t offset = address - baseAddress_;
1533 if (offset + 8 > rawData_.size())
1534 return 0;
1535 return *reinterpret_cast<const uint64_t *>(&rawData_[offset]);
1536}
1537 
1538int32_t DecompilerContext::ReadInt32(uint64_t address) const {
1539 if (address < baseAddress_)
1540 return 0;
1541 uint64_t offset = address - baseAddress_;
1542 if (offset + 4 > rawData_.size())
1543 return 0;
1544 return *reinterpret_cast<const int32_t *>(&rawData_[offset]);
1545}
1546 
1547std::shared_ptr<IR::Function>
1548DecompilerContext::GetFunctionAt(uint64_t address) {
1549 for (const auto &func : functions_) {
1550 if (func->address == address)
1551 return func;
1552 }
1553 return nullptr;
1554}
1555 
1556bool DecompilerContext::SaveProject(const std::string &path) {
1557 json j;
1558 
1559 // Symbols
1560 if (symbolDatabase_) {
1561 for (const auto &[addr, sym] : symbolDatabase_->getSymbols()) {
1562 j["symbols"].push_back({{"address", addr},
1563 {"name", sym.name},
1564 {"type", (int)sym.type},
1565 {"source", (int)sym.source}});
1566 }
1567 }
1568 
1569 // Structs
1570 for (const auto &[name, type] : typeManager_->getAllTypes()) {
1571 if (type->getKind() == Analysis::Type::Kind::Struct) {
1572 auto s = std::dynamic_pointer_cast<Analysis::StructType>(type);
1573 json sJson;
1574 sJson["name"] = name;
1575 for (const auto &m : s->getMembers()) {
1576 sJson["members"].push_back({{"name", m.name},
1577 {"offset", m.offset},
1578 {"type", m.type->toString()}});
1579 }
1580 j["structs"].push_back(sJson);
1581 }
1582 }
1583 
1584 // User Overrides (Stack Types)
1585 // userVarTypes_: FunctionAddr -> (StackOffset -> Type)
1586 for (const auto &[funcAddr, offsets] : userVarTypes_) {
1587 for (const auto &[offset, type] : offsets) {
1588 j["overrides"].push_back(
1589 {{"func", funcAddr}, {"offset", offset}, {"type", type->toString()}});
1590 }
1591 }
1592 
1593 std::ofstream o(path);
1594 o << std::setw(4) << j << std::endl;
1595 return true;
1596}
1597 
1598bool DecompilerContext::LoadProject(const std::string &path) {
1599 std::ifstream i(path);
1600 if (!i.is_open())
1601 return false;
1602 
1603 json j;
1604 i >> j;
1605 
1606 // Symbols
1607 if (j.contains("symbols") && symbolDatabase_) {
1608 symbolDatabase_->clear(); // Maybe clear existing?
1609 for (const auto &sym : j["symbols"]) {
1610 symbolDatabase_->addSymbol(sym["address"], sym["name"],
1611 (Analysis::SymbolType)sym["type"],
1612 (Analysis::SymbolSource)sym["source"]);
1613 }
1614 }
1615 
1616 // Structs
1617 if (j.contains("structs")) {
1618 for (const auto &sJson : j["structs"]) {
1619 std::string name = sJson["name"];
1620 // Use createStruct which registers it
1621 auto s = typeManager_->createStruct(name);
1622 for (const auto &mJson : sJson["members"]) {
1623 std::string typeName = mJson["type"];
1624 auto type = typeManager_->getType(typeName);
1625 if (!type) {
1626 // Basic primitive fallback or implicit creation
1627 if (typeName.back() == '*') {
1628 // Assume pointer to something?
1629 // For now default to int
1630 type = typeManager_->getType("int");
1631 } else {
1632 type = typeManager_->getType("int");
1633 }
1634 }
1635 if (type) {
1636 s->addMember(mJson["name"], type, mJson["offset"]);
1637 }
1638 }
1639 }
1640 }
1641 
1642 // Overrides
1643 if (j.contains("overrides")) {
1644 for (const auto &ov : j["overrides"]) {
1645 uint64_t funcAddr = ov["func"];
1646 int offset = ov["offset"];
1647 std::string typeName = ov["type"];
1648 
1649 auto type = typeManager_->getType(typeName);
1650 if (type) {
1651 SetUserVarType(funcAddr, offset, type);
1652 }
1653 }
1654 }
1655 
1656 return true;
1657}
1658 
1659} // namespace ShadPKG::Decompiler