Seregon/ShadPKG

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

C++/47.3 KB/No license
core/decompiler/analysis/StructuralAnalysis.cpp
ShadPKG / core / decompiler / analysis / StructuralAnalysis.cpp
1#include "StructuralAnalysis.h"
2#include <iostream>
3 
4namespace ShadPKG::Decompiler::Analysis {
5 
6StructuralAnalysis::StructuralAnalysis(
7 std::shared_ptr<IR::Function> func,
8 std::shared_ptr<DominatorAnalysis> domAnalysis,
9 std::shared_ptr<SymbolAnalysis> symbolAnalysis,
10 std::shared_ptr<Lifter::VariableAnalysis> varAnalysis)
11 : func_(func), dom_(domAnalysis), symbols_(symbolAnalysis),
12 vars_(varAnalysis) {}
13 
14std::shared_ptr<AST::FunctionAST> StructuralAnalysis::analyze() {
15 auto ast = std::make_shared<AST::FunctionAST>();
16 ast->name = func_->name;
17 ast->address = func_->address;
18 
19 // Pre-pass: identify all blocks that are jump targets (need labels)
20 // Only include targets that actually exist in this function
21 std::set<uint64_t> validBlockIds;
22 for (const auto &bb : func_->basicBlocks) {
23 validBlockIds.insert(bb->id);
24 }
25 for (const auto &bb : func_->basicBlocks) {
26 for (uint64_t succ : bb->successors) {
27 // Only add if the target block exists in this function
28 if (validBlockIds.count(succ) && bb->successors.size() > 1) {
29 gotoTargets_.insert(succ);
30 }
31 }
32 }
33 
34 if (!func_->basicBlocks.empty()) {
35 uint64_t entry = func_->basicBlocks[0]->id;
36 auto stmt = structureRegion(entry, 0);
37 
38 if (stmt) {
39 if (auto compound =
40 std::dynamic_pointer_cast<AST::CompoundStatement>(stmt)) {
41 ast->body = compound;
42 } else {
43 ast->body->addStatement(stmt);
44 }
45 }
46 }
47 
48 return ast;
49}
50 
51std::shared_ptr<IR::BasicBlock> StructuralAnalysis::getBlock(uint64_t id) {
52 for (const auto &bb : func_->basicBlocks) {
53 if (bb->id == id)
54 return bb;
55 }
56 return nullptr;
57}
58 
59std::shared_ptr<AST::Statement>
60StructuralAnalysis::structureRegion(uint64_t entryBlock, uint64_t stopBlock) {
61 if (entryBlock == 0 || entryBlock == stopBlock)
62 return nullptr;
63 
64 auto sequence = std::make_shared<AST::CompoundStatement>();
65 uint64_t current = entryBlock;
66 
67 while (current != 0 && current != stopBlock) {
68 if (structuredBlocks_.count(current)) {
69 // Only emit goto if the target block exists in this function
70 if (current != 0xffffffffffffffff && getBlock(current) != nullptr) {
71 gotoTargets_.insert(current);
72 sequence->addStatement(std::make_shared<AST::GotoStatement>(current));
73 }
74 break;
75 }
76 
77 // Check for Loop Header, but avoid infinite recursion if we are already
78 // analyzing this loop
79 if (dom_->isLoopHeader(current) && !activeLoops_.count(current)) {
80 activeLoops_.insert(current);
81 auto loopStmt = matchLoop(current, stopBlock);
82 activeLoops_.erase(current);
83 
84 if (loopStmt) {
85 structuredBlocks_.insert(current);
86 sequence->addStatement(loopStmt);
87 const auto *loopInfo = dom_->getLoopFor(current);
88 uint64_t ipdom = dom_->getImmediatePostDominator(current);
89 while (ipdom != 0 && loopInfo->contains(ipdom)) {
90 ipdom = dom_->getImmediatePostDominator(ipdom);
91 }
92 current = ipdom;
93 continue;
94 }
95 }
96 
97 // Mark as visited NOW (after loop attempt)
98 structuredBlocks_.insert(current);
99 
100 auto bb = getBlock(current);
101 if (bb && bb->successors.size() == 2) {
102 auto ifStmt = matchIf(current, stopBlock);
103 if (ifStmt) {
104 structuredBlocks_.insert(current);
105 sequence->addStatement(ifStmt);
106 uint64_t ipdom = dom_->getImmediatePostDominator(current);
107 current = ipdom;
108 continue;
109 }
110 }
111 
112 if (bb && bb->successors.size() > 2) {
113 auto switchStmt = matchSwitch(current, stopBlock);
114 if (switchStmt) {
115 structuredBlocks_.insert(current);
116 sequence->addStatement(switchStmt);
117 uint64_t ipdom = dom_->getImmediatePostDominator(current);
118 current = ipdom;
119 continue;
120 }
121 }
122 
123 if (bb) {
124 auto blockStmt = structureBlock(bb);
125 for (auto &s : blockStmt->statements) {
126 sequence->addStatement(s);
127 }
128 if (bb->successors.empty()) {
129 current = 0;
130 } else if (bb->successors.size() == 1) {
131 current = bb->successors[0];
132 } else {
133 current =
134 0; // Unhandled branching implies end of region or confusing CFG
135 }
136 } else {
137 break;
138 }
139 }
140 
141 if (sequence->statements.size() == 1) {
142 return sequence->statements[0];
143 }
144 
145 return sequence;
146}
147 
148std::shared_ptr<AST::Statement>
149StructuralAnalysis::matchLoop(uint64_t header, uint64_t stopBlock) {
150 const auto *loopInfo = dom_->getLoopFor(header);
151 if (!loopInfo)
152 return nullptr;
153 
154 auto headerBB = getBlock(header);
155 std::shared_ptr<AST::Statement> result = nullptr;
156 
157 if (loopInfo->isDoWhile) {
158 // ┌───────────────────────────────────────────────────────────────────┐
159 // │ DO-WHILE: Extract condition from latch block (loop tail) │
160 // └───────────────────────────────────────────────────────────────────┘
161 uint64_t latchBlock = loopInfo->latchBlock;
162 auto latchBB = getBlock(latchBlock);
163 auto condition = latchBB ? extractCondition(latchBB, false)
164 : std::make_shared<AST::ConstantExpr>((int64_t)1);
165 
166 // If header == latch, structureRegion(header, latch) would return nullptr
167 // (entry == stop). pass 0 as stopBlock to allow processing the header-body.
168 uint64_t effectiveStop = (header == latchBlock) ? 0 : latchBlock;
169 auto bodyStmt = structureRegion(header, effectiveStop);
170 auto body = std::dynamic_pointer_cast<AST::CompoundStatement>(bodyStmt);
171 if (!body) {
172 body = std::make_shared<AST::CompoundStatement>();
173 if (bodyStmt)
174 body->addStatement(bodyStmt);
175 }
176 result = std::make_shared<AST::DoWhileStatement>(body, condition);
177 } else {
178 if (headerBB->successors.size() == 2) {
179 uint64_t trueSucc = headerBB->successors[0];
180 uint64_t falseSucc = headerBB->successors[1];
181 bool inverted = false;
182 if (!loopInfo->contains(trueSucc)) {
183 std::swap(trueSucc, falseSucc);
184 inverted = true;
185 }
186 auto condition = extractCondition(headerBB, inverted);
187 auto bodyStmt = structureRegion(trueSucc, header);
188 result = std::make_shared<AST::WhileStatement>(condition, bodyStmt);
189 }
190 }
191 
192 if (result) {
193 // For While loops (non-DoWhile), the header block is NOT part of the body.
194 // We must emit the header's label and instructions before the loop.
195 if (!loopInfo->isDoWhile) {
196 auto compound = std::make_shared<AST::CompoundStatement>();
197 compound->addStatement(structureBlock(headerBB));
198 compound->addStatement(result);
199 return compound;
200 }
201 return result;
202 }
203 
204 return nullptr;
205}
206 
207std::shared_ptr<AST::Statement>
208StructuralAnalysis::matchIf(uint64_t block, uint64_t stopBlock) {
209 auto bb = getBlock(block);
210 uint64_t trueSucc = bb->successors[0];
211 uint64_t falseSucc = bb->successors[1];
212 uint64_t ipdom = dom_->getImmediatePostDominator(block);
213 auto condition = extractCondition(bb, false);
214 
215 std::shared_ptr<AST::Statement> ifStmt = nullptr;
216 if (falseSucc == ipdom) {
217 auto thenStmt = structureRegion(trueSucc, ipdom);
218 ifStmt = std::make_shared<AST::IfStatement>(condition, thenStmt, nullptr);
219 } else if (trueSucc == ipdom) {
220 auto invertedCond = extractCondition(bb, true);
221 auto thenStmt = structureRegion(falseSucc, ipdom);
222 ifStmt =
223 std::make_shared<AST::IfStatement>(invertedCond, thenStmt, nullptr);
224 } else {
225 auto thenStmt = structureRegion(trueSucc, ipdom);
226 auto elseStmt = structureRegion(falseSucc, ipdom);
227 ifStmt = std::make_shared<AST::IfStatement>(condition, thenStmt, elseStmt);
228 }
229 
230 // Always wrap IfStatement with its header block's label and instructions
231 auto compound = std::make_shared<AST::CompoundStatement>();
232 compound->addStatement(structureBlock(bb));
233 compound->addStatement(ifStmt);
234 return compound;
235}
236 
237std::shared_ptr<AST::Statement>
238StructuralAnalysis::matchSwitch(uint64_t block, uint64_t stopBlock) {
239 auto bb = getBlock(block);
240 if (!bb)
241 return nullptr;
242 
243 uint64_t ipdom = dom_->getImmediatePostDominator(block);
244 if (ipdom == 0)
245 ipdom = stopBlock;
246 
247 auto condition = std::make_shared<AST::VariableExpr>("switch_val");
248 auto switchStmt = std::make_shared<AST::SwitchStmt>(condition);
249 std::map<uint64_t, std::vector<int64_t>> targetToValues;
250 for (const auto &[val, target] : bb->switchMap) {
251 targetToValues[target].push_back(val);
252 }
253 
254 for (const auto &[targetId, values] : targetToValues) {
255 auto caseStmt = std::make_shared<AST::CaseStmt>();
256 caseStmt->values = values;
257 auto stmt = structureRegion(targetId, ipdom);
258 if (auto compound =
259 std::dynamic_pointer_cast<AST::CompoundStatement>(stmt)) {
260 caseStmt->body = compound;
261 } else {
262 caseStmt->body = std::make_shared<AST::CompoundStatement>();
263 if (stmt)
264 caseStmt->body->addStatement(stmt);
265 }
266 caseStmt->body->addStatement(std::make_shared<AST::BreakStatement>());
267 switchStmt->cases.push_back(caseStmt);
268 }
269 
270 // Wrap SwitchStmt with its header block's label and instructions
271 auto result = std::make_shared<AST::CompoundStatement>();
272 result->addStatement(structureBlock(bb));
273 result->addStatement(switchStmt);
274 return result;
275}
276 
277std::shared_ptr<AST::CompoundStatement>
278StructuralAnalysis::structureBlock(const std::shared_ptr<IR::BasicBlock> &bb) {
279 auto compound = std::make_shared<AST::CompoundStatement>();
280 
281 // Emit label if this block is referenced by any goto statement
282 // This includes: conditional jumps, unconditional jumps, loop headers, switch targets
283 bool needsLabel = false;
284
285 // Check if any block has this as a successor (potential jump target)
286 for (const auto &otherBB : func_->basicBlocks) {
287 if (otherBB->id == bb->id) continue;
288
289 // Check if this block is a non-fallthrough successor
290 for (size_t i = 0; i < otherBB->successors.size(); i++) {
291 if (otherBB->successors[i] == bb->id) {
292 // If it's a conditional branch or not the first successor, it needs a label
293 if (otherBB->successors.size() > 1 || i > 0) {
294 needsLabel = true;
295 break;
296 }
297 // Check if it's a back edge (target address < source address)
298 if (bb->id < otherBB->id) {
299 needsLabel = true;
300 break;
301 }
302 }
303 }
304 if (needsLabel) break;
305 }
306
307 // Loop headers always need labels
308 if (dom_ && dom_->isLoopHeader(bb->id)) {
309 needsLabel = true;
310 }
311
312 // Blocks that are targets of goto statements need labels
313 if (gotoTargets_.count(bb->id)) {
314 needsLabel = true;
315 }
316
317 if (needsLabel && !emittedLabels_.count(bb->id)) {
318 std::stringstream ss;
319 ss << "loc_" << std::hex << bb->id;
320 auto labelStmt = std::make_shared<AST::LabelStatement>(ss.str());
321 labelStmt->address = bb->id;
322 compound->addStatement(labelStmt);
323 emittedLabels_.insert(bb->id);
324 }
325 
326 // Local state for call argument detection
327 std::map<std::string, std::shared_ptr<AST::Expression>> regValues;
328 
329 for (size_t i = 0; i < bb->instructions.size(); ++i) {
330 const auto &instr = bb->instructions[i];
331 bool isLast = (i == bb->instructions.size() - 1);
332 if (isLast) {
333 if (instr.opcode >= IR::OpCode::JMP && instr.opcode <= IR::OpCode::RET) {
334 continue;
335 }
336 }
337 if (i + 1 < bb->instructions.size()) {
338 const auto &next = bb->instructions[i + 1];
339 if ((instr.opcode == IR::OpCode::CMP ||
340 instr.opcode == IR::OpCode::AND) &&
341 (next.opcode >= IR::OpCode::JE && next.opcode <= IR::OpCode::JNO)) {
342 continue;
343 }
344 }
345 
346 auto astStmt = liftInstruction(instr);
347 if (astStmt) {
348 astStmt->sourceAddress = instr.address;
349 astStmt->comment = instr.disassembly;
350 compound->addStatement(astStmt);
351 
352 // Track assignments for call argument detection
353 if (auto exprStmt =
354 std::dynamic_pointer_cast<AST::ExpressionStatement>(astStmt)) {
355 if (auto binExpr = std::dynamic_pointer_cast<AST::BinaryExpr>(
356 exprStmt->expression)) {
357 if (binExpr->op == AST::BinaryExpr::Op::Assign) {
358 if (auto var = std::dynamic_pointer_cast<AST::VariableExpr>(
359 binExpr->left)) {
360 regValues[var->name] = binExpr->right;
361 }
362 }
363 }
364 }
365 
366 // Special handling for CALL: inject arguments if detected
367 if (auto exprStmt =
368 std::dynamic_pointer_cast<AST::ExpressionStatement>(astStmt)) {
369 if (auto call = std::dynamic_pointer_cast<AST::CallExpr>(
370 exprStmt->expression)) {
371 // Skip __asm__ volatile calls - they don't take register arguments
372 if (call->functionName.find("__asm__") != std::string::npos) {
373 continue;
374 }
375 // System V AMD64 ABI: rdi, rsi, rdx, rcx, r8, r9 for integer args
376 static const std::vector<std::string> paramRegs = {
377 "reg_rdi", "reg_rsi", "reg_rdx", "reg_rcx", "reg_r8", "reg_r9"};
378 for (const auto &rName : paramRegs) {
379 if (regValues.count(rName)) {
380 call->arguments.push_back(regValues[rName]);
381 }
382 }
383 }
384 }
385 }
386 }
387 
388 return compound;
389}
390 
391std::shared_ptr<AST::Expression>
392StructuralAnalysis::extractCondition(const std::shared_ptr<IR::BasicBlock> &bb,
393 bool invert) {
394 if (bb->instructions.empty())
395 return std::make_shared<AST::ConstantExpr>((int64_t)1);
396 
397 const auto &last = bb->instructions.back();
398 const IR::Instruction *flagSetter = nullptr;
399 for (auto it = bb->instructions.rbegin(); it != bb->instructions.rend();
400 ++it) {
401 if (it->opcode == IR::OpCode::CMP || it->opcode == IR::OpCode::AND) {
402 flagSetter = &(*it);
403 break;
404 }
405 }
406 
407 if (!flagSetter)
408 return std::make_shared<AST::ConstantExpr>((int64_t)1);
409 
410 std::shared_ptr<AST::Expression> left =
411 std::make_shared<AST::VariableExpr>("var_xx");
412 std::shared_ptr<AST::Expression> right =
413 std::make_shared<AST::VariableExpr>("var_yy");
414 
415 if (flagSetter->operands.size() >= 2) {
416 auto liftOperand =
417 [&](const IR::Operand &op) -> std::shared_ptr<AST::Expression> {
418 if (op.type == IR::Operand::Type::Register) {
419 if (vars_) {
420 std::string pName = vars_->getParamForRegister(op.value);
421 if (!pName.empty())
422 return std::make_shared<AST::VariableExpr>(pName);
423 }
424 // Use reg_ prefix so we can declare it as a variable
425 std::string regName = op.regName.empty()
426 ? "reg_" + std::to_string(op.value)
427 : "reg_" + op.regName;
428 return std::make_shared<AST::VariableExpr>(regName);
429 }
430 if (op.type == IR::Operand::Type::Immediate)
431 return std::make_shared<AST::ConstantExpr>((int64_t)op.value, true);
432 if (op.type == IR::Operand::Type::Memory) {
433 if (vars_) {
434 std::string v = vars_->resolveVariable(op);
435 if (!v.empty())
436 return std::make_shared<AST::VariableExpr>(v);
437 }
438 // Fallback for memory: *(int64_t*)(base + size)
439 
440 std::shared_ptr<AST::Expression> baseExpr;
441 if (!op.memBaseName.empty()) {
442 baseExpr =
443 std::make_shared<AST::VariableExpr>("reg_" + op.memBaseName);
444 } else if (op.name == "RIP") {
445 baseExpr = std::make_shared<AST::VariableExpr>("RIP");
446 } else if (op.name == "RBP") {
447 baseExpr = std::make_shared<AST::VariableExpr>("RBP");
448 } else {
449 baseExpr = std::make_shared<AST::ConstantExpr>((int64_t)0);
450 }
451 
452 if (op.memDisp != 0) {
453 auto disp = std::make_shared<AST::ConstantExpr>((int64_t)op.memDisp);
454 // Handle negative displacement for cleaner AST if needed, but
455 // ConstantExpr supports negative
456 baseExpr = std::make_shared<AST::BinaryExpr>(AST::BinaryExpr::Op::Add,
457 baseExpr, disp);
458 }
459 
460 // Wrap with ps4_resolve() so PS4 virtual addresses in registers are
461 // safely mapped into g_ps4_memory (same fix as in liftInstruction)
462 auto resolveCall = std::make_shared<AST::CallExpr>("ps4_resolve");
463 resolveCall->arguments.push_back(baseExpr);
464 auto castExpr = std::make_shared<AST::CastExpr>(resolveCall, "int64_t*");
465 return std::make_shared<AST::UnaryExpr>(AST::UnaryExpr::Op::Deref,
466 castExpr);
467 }
468 return std::make_shared<AST::VariableExpr>("unknown_op");
469 };
470 left = liftOperand(flagSetter->operands[0]);
471 right = liftOperand(flagSetter->operands[1]);
472 }
473 
474 AST::BinaryExpr::Op op = AST::BinaryExpr::Op::Ne; // default: != 0
475 switch (last.opcode) {
476 case IR::OpCode::JE:
477 op = AST::BinaryExpr::Op::Eq;
478 break;
479 case IR::OpCode::JNE:
480 op = AST::BinaryExpr::Op::Ne;
481 break;
482 case IR::OpCode::JG:
483 case IR::OpCode::JA: // unsigned above ~ signed greater (approximation)
484 op = AST::BinaryExpr::Op::Gt;
485 break;
486 case IR::OpCode::JGE:
487 case IR::OpCode::JAE: // unsigned above-or-equal
488 case IR::OpCode::JNS: // not sign = >= 0
489 case IR::OpCode::JNO: // not overflow
490 op = AST::BinaryExpr::Op::Ge;
491 break;
492 case IR::OpCode::JL:
493 case IR::OpCode::JB: // unsigned below ~ signed less (approximation)
494 case IR::OpCode::JS: // sign set = < 0
495 case IR::OpCode::JO: // overflow
496 op = AST::BinaryExpr::Op::Lt;
497 break;
498 case IR::OpCode::JLE:
499 case IR::OpCode::JBE: // unsigned below-or-equal
500 op = AST::BinaryExpr::Op::Le;
501 break;
502 default:
503 op = AST::BinaryExpr::Op::Ne;
504 break;
505 }
506 
507 if (invert) {
508 switch (op) {
509 case AST::BinaryExpr::Op::Eq:
510 op = AST::BinaryExpr::Op::Ne;
511 break;
512 case AST::BinaryExpr::Op::Ne:
513 op = AST::BinaryExpr::Op::Eq;
514 break;
515 case AST::BinaryExpr::Op::Gt:
516 op = AST::BinaryExpr::Op::Le;
517 break;
518 case AST::BinaryExpr::Op::Lt:
519 op = AST::BinaryExpr::Op::Ge;
520 break;
521 case AST::BinaryExpr::Op::Ge:
522 op = AST::BinaryExpr::Op::Lt;
523 break;
524 case AST::BinaryExpr::Op::Le:
525 op = AST::BinaryExpr::Op::Gt;
526 break;
527 default:
528 break;
529 }
530 }
531 
532 return std::make_shared<AST::BinaryExpr>(op, left, right);
533}
534 
535std::shared_ptr<AST::Statement>
536StructuralAnalysis::liftInstruction(const IR::Instruction &instr) {
537 auto liftOp = [&](const IR::Operand &op) -> std::shared_ptr<AST::Expression> {
538 if (op.type == IR::Operand::Type::Register) {
539 if (vars_) {
540 std::string pName = vars_->getParamForRegister(op.value);
541 if (!pName.empty())
542 return std::make_shared<AST::VariableExpr>(pName);
543 }
544 // Use reg_ prefix so we can declare it as a variable
545 std::string regName = op.regName.empty()
546 ? "reg_" + std::to_string(op.value)
547 : "reg_" + op.regName;
548 return std::make_shared<AST::VariableExpr>(regName);
549 }
550 if (op.type == IR::Operand::Type::Immediate) {
551 if (symbols_) {
552 auto str = symbols_->getStringLiteral(op.value);
553 if (str)
554 return std::make_shared<AST::ConstantExpr>("\"" + *str + "\"");
555 }
556 return std::make_shared<AST::ConstantExpr>((int64_t)op.value, true);
557 }
558 if (op.type == IR::Operand::Type::Memory) {
559 if (op.value != 0 && symbols_) {
560 auto str = symbols_->getStringLiteral(op.value);
561 if (str)
562 return std::make_shared<AST::ConstantExpr>("\"" + *str + "\"");
563 }
564 if (vars_) {
565 std::string varName = vars_->resolveVariable(op);
566 if (!varName.empty())
567 return std::make_shared<AST::VariableExpr>(varName);
568 }
569 
570 // RIP-relative access: global variable or constant
571 if (op.memBaseName == "rip" && op.value != 0) {
572 if (symbols_) {
573 auto symInfo = symbols_->getSymbol(op.value);
574 // Only resolve if it's explicitly a GlobalVariable.
575 if (symInfo &&
576 symInfo->type == Analysis::SymbolType::GlobalVariable) {
577 return std::make_shared<AST::VariableExpr>(symInfo->name);
578 }
579 }
580 // Generate access via global memory buffer: *((int64_t*)&g_ps4_memory[addr])
581 std::stringstream hexAddr;
582 hexAddr << std::hex << op.value;
583 auto memAccess = std::make_shared<AST::VariableExpr>(
584 "g_ps4_memory[0x" + hexAddr.str() + "]");
585 auto addrOf = std::make_shared<AST::UnaryExpr>(
586 AST::UnaryExpr::Op::AddressOf, memAccess);
587 auto castExpr = std::make_shared<AST::CastExpr>(addrOf, "int64_t*");
588 return std::make_shared<AST::UnaryExpr>(AST::UnaryExpr::Op::Deref,
589 castExpr);
590 }
591 
592 if (!op.memBaseName.empty() || op.name == "RBP" || op.name == "RSP") {
593 std::shared_ptr<AST::Expression> baseExpr;
594 if (!op.memBaseName.empty()) {
595 baseExpr =
596 std::make_shared<AST::VariableExpr>("reg_" + op.memBaseName);
597 } else if (op.name == "RBP") {
598 baseExpr = std::make_shared<AST::VariableExpr>("RBP");
599 } else {
600 baseExpr = std::make_shared<AST::VariableExpr>("reg_" + op.name);
601 }
602 
603 if (op.memDisp != 0) {
604 auto disp = std::make_shared<AST::ConstantExpr>((int64_t)op.memDisp);
605 baseExpr = std::make_shared<AST::BinaryExpr>(AST::BinaryExpr::Op::Add,
606 baseExpr, disp);
607 }
608 
609 // Determine pointer type based on instruction context
610 std::string ptrType = "int64_t*";
611 std::string disasm = instr.disassembly;
612 if (disasm.find("ss") != std::string::npos ||
613 disasm.find("movss") != std::string::npos ||
614 disasm.find("vmovss") != std::string::npos) {
615 ptrType = "float*";
616 } else if (disasm.find("sd") != std::string::npos ||
617 disasm.find("movsd") != std::string::npos) {
618 ptrType = "double*";
619 } else if (disasm.find("dword") != std::string::npos) {
620 ptrType = "int32_t*";
621 } else if (disasm.find("word ptr") != std::string::npos) {
622 ptrType = "int16_t*";
623 } else if (disasm.find("byte ptr") != std::string::npos) {
624 ptrType = "int8_t*";
625 }
626 
627 // Wrap with ps4_resolve() so PS4 virtual addresses are safely mapped
628 // into g_ps4_memory instead of being used as raw host pointers.
629 auto resolveCall = std::make_shared<AST::CallExpr>("ps4_resolve");
630 resolveCall->arguments.push_back(baseExpr);
631 auto castExpr = std::make_shared<AST::CastExpr>(resolveCall, ptrType);
632 return std::make_shared<AST::UnaryExpr>(AST::UnaryExpr::Op::Deref,
633 castExpr);
634 }
635 if (op.value != 0) {
636 // Generate access via global memory buffer: *((int64_t*)&g_ps4_memory[addr])
637 std::stringstream hexAddr;
638 hexAddr << std::hex << op.value;
639 auto memAccess = std::make_shared<AST::VariableExpr>(
640 "g_ps4_memory[0x" + hexAddr.str() + "]");
641 auto addrOf = std::make_shared<AST::UnaryExpr>(
642 AST::UnaryExpr::Op::AddressOf, memAccess);
643 auto castExpr = std::make_shared<AST::CastExpr>(addrOf, "int64_t*");
644 return std::make_shared<AST::UnaryExpr>(AST::UnaryExpr::Op::Deref,
645 castExpr);
646 }
647 return std::make_shared<AST::ConstantExpr>((int64_t)0);
648 }
649 return std::make_shared<AST::VariableExpr>("?");
650 };
651 
652 if (instr.opcode == IR::OpCode::PUSH || instr.opcode == IR::OpCode::POP)
653 return nullptr;
654 
655 if (instr.opcode == IR::OpCode::LEAVE)
656 return nullptr; // Remove leave instruction
657 
658 if (instr.opcode == IR::OpCode::ADD || instr.opcode == IR::OpCode::SUB) {
659 if (instr.operands.size() >= 1 &&
660 instr.operands[0].type == IR::Operand::Type::Register) {
661 if (instr.operands[0].regName == "rsp")
662 return nullptr;
663 }
664 }
665 
666 if (instr.opcode == IR::OpCode::MOV || instr.opcode == IR::OpCode::LEA) {
667 if (instr.operands.size() >= 2) {
668 auto left = liftOp(instr.operands[0]);
669 auto right = liftOp(instr.operands[1]);
670 bool isLValue = std::dynamic_pointer_cast<AST::VariableExpr>(left) ||
671 std::dynamic_pointer_cast<AST::MemoryExpr>(left);
672 if (isLValue) {
673 return std::make_shared<AST::ExpressionStatement>(
674 std::make_shared<AST::BinaryExpr>(AST::BinaryExpr::Op::Assign, left,
675 right));
676 }
677 }
678 }
679 
680 if (instr.opcode == IR::OpCode::MOVSX || instr.opcode == IR::OpCode::MOVZX) {
681 std::string castType =
682 (instr.opcode == IR::OpCode::MOVSX) ? "int64_t" : "uint64_t";
683 if (instr.operands.size() >= 2) {
684 auto left = liftOp(instr.operands[0]);
685 auto right = liftOp(instr.operands[1]);
686 auto castExpr = std::make_shared<AST::CastExpr>(right, castType);
687 return std::make_shared<AST::ExpressionStatement>(
688 std::make_shared<AST::BinaryExpr>(AST::BinaryExpr::Op::Assign, left,
689 castExpr));
690 }
691 }
692 
693 if (instr.opcode == IR::OpCode::BSWAP) {
694 if (instr.operands.size() == 1) {
695 auto operand = liftOp(instr.operands[0]);
696 auto call = std::make_shared<AST::CallExpr>(
697 "_byteswap_uint64"); // Assuming 64-bit for now
698 call->arguments.push_back(operand);
699 return std::make_shared<AST::ExpressionStatement>(
700 std::make_shared<AST::BinaryExpr>(AST::BinaryExpr::Op::Assign,
701 operand, call));
702 }
703 }
704 
705 if (instr.opcode == IR::OpCode::FISTTP) {
706 if (instr.operands.size() == 1) {
707 auto operand = liftOp(instr.operands[0]);
708 std::string castType = "int"; // Needs better type inference
709 auto castExpr = std::make_shared<AST::CastExpr>(operand, castType);
710 return std::make_shared<AST::ExpressionStatement>(castExpr);
711 }
712 }
713 
714 auto liftBinary =
715 [&](AST::BinaryExpr::Op op) -> std::shared_ptr<AST::Statement> {
716 if (instr.operands.size() == 3) {
717 auto dst = liftOp(instr.operands[0]);
718 auto src1 = liftOp(instr.operands[1]);
719 auto src2 = liftOp(instr.operands[2]);
720 auto expr = std::make_shared<AST::BinaryExpr>(op, src1, src2);
721 auto assign = std::make_shared<AST::BinaryExpr>(
722 AST::BinaryExpr::Op::Assign, dst, expr);
723 return std::make_shared<AST::ExpressionStatement>(assign);
724 } else if (instr.operands.size() == 2) {
725 auto dst = liftOp(instr.operands[0]);
726 auto src = liftOp(instr.operands[1]);
727 auto expr = std::make_shared<AST::BinaryExpr>(op, dst, src);
728 auto assign = std::make_shared<AST::BinaryExpr>(
729 AST::BinaryExpr::Op::Assign, dst, expr);
730 return std::make_shared<AST::ExpressionStatement>(assign);
731 }
732 return nullptr;
733 };
734 
735 switch (instr.opcode) {
736 case IR::OpCode::ADD:
737 return liftBinary(AST::BinaryExpr::Op::Add);
738 case IR::OpCode::SUB:
739 return liftBinary(AST::BinaryExpr::Op::Sub);
740 case IR::OpCode::AND:
741 return liftBinary(AST::BinaryExpr::Op::And);
742 case IR::OpCode::OR:
743 return liftBinary(AST::BinaryExpr::Op::Or);
744 case IR::OpCode::XOR:
745 return liftBinary(AST::BinaryExpr::Op::Xor);
746 case IR::OpCode::SHL:
747 return liftBinary(AST::BinaryExpr::Op::Shl);
748 case IR::OpCode::SHR:
749 return liftBinary(AST::BinaryExpr::Op::Shr);
750 case IR::OpCode::MUL:
751 return liftBinary(AST::BinaryExpr::Op::Mul);
752 case IR::OpCode::DIV:
753 return liftBinary(AST::BinaryExpr::Op::Div);
754 default:
755 break;
756 }
757 
758 if (instr.opcode == IR::OpCode::CALL) {
759 if (!instr.operands.empty()) {
760 // ── Direct call (immediate target) ───────────────────────────────────
761 if (instr.operands[0].type == IR::Operand::Type::Immediate) {
762 uint64_t target = instr.operands[0].value;
763 std::string funcName;
764 if (symbols_) {
765 auto parsedName = symbols_->getFunctionName(target);
766 if (parsedName)
767 funcName = *parsedName;
768 }
769 if (funcName.empty()) {
770 std::stringstream ss;
771 ss << "sub_" << std::hex << target;
772 funcName = ss.str();
773 }
774 auto callExpr = std::make_shared<AST::CallExpr>(target);
775 callExpr->functionName = funcName;
776 return std::make_shared<AST::ExpressionStatement>(callExpr);
777 }
778 
779 // ── Indirect call (register or memory target) ──────────────────────
780 // e.g. call rax -> call reg_rax
781 // call [rax + 0x18] -> call *(int64_t*)(reg_rax + 0x18)
782 // Emit: ps4_indirect_call(<target>, rdi, rsi, rdx, rcx, r8, r9)
783 auto targetExpr = liftOp(instr.operands[0]);
784 auto call = std::make_shared<AST::CallExpr>("ps4_indirect_call");
785 // Cast target to void*
786 call->arguments.push_back(
787 std::make_shared<AST::CastExpr>(targetExpr, "void*"));
788 // Inject ABI argument registers if they were recently assigned
789 // (regValues is tracked in structureBlock and injected after this return)
790 return std::make_shared<AST::ExpressionStatement>(call);
791 }
792 }
793 
794 // ┌───────────────────────────────────────────────────────────────────┐
795 // │ FALLBACK: Skip unrecognized instructions that are likely noise │
796 // │ Only emit truly important ones as comments │
797 // └───────────────────────────────────────────────────────────────────┘
798
799 // Skip common noise instructions
800 std::string disasm = instr.disassembly;
801 if (disasm.find("nop") != std::string::npos ||
802 disasm.find("endbr") != std::string::npos ||
803 disasm.find("int3") != std::string::npos ||
804 disasm.find("ud2") != std::string::npos) {
805 return nullptr; // Skip these entirely
806 }
807
808 // For potentially important unhandled instructions, emit as comment
809 // This preserves debug info without breaking compilation
810 auto comment = std::make_shared<AST::VariableExpr>("/* " + disasm + " */");
811 return std::make_shared<AST::ExpressionStatement>(comment);
812}
813 
814} // namespace ShadPKG::Decompiler::Analysis