Seregon/ShadPKG

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

C++/47.3 KB/No license
gui/views/DecompilerView.cpp
ShadPKG / gui / views / DecompilerView.cpp
1#include "gui/include/views/DecompilerView.h"
2#include "common/io_file.h"
3#include "gui/include/StyleManager.h"
4#include "imgui.h"
5#include <sstream>
6 
7namespace ShadPKG::GUI {
8 
9DecompilerView::DecompilerView() {
10 decompilerCtx_ = std::make_unique<ShadPKG::Decompiler::DecompilerContext>();
11 decompilerCtx_ = std::make_unique<ShadPKG::Decompiler::DecompilerContext>();
12 retypeSearchBuffer_.resize(256);
13}
14 
15void DecompilerView::LoadBinary(const std::string &path) {
16 // Unused
17}
18 
19void DecompilerView::Draw(GUIContext &ctx) {
20 // Zero-Friction: Detect loaded PKG
21 if (ctx.currentPkg != lastPkg_) {
22 lastPkg_ = ctx.currentPkg;
23 selectedFunction_ = nullptr;
24 
25 if (lastPkg_) {
26 // Find executable
27 std::vector<u8> buffer = lastPkg_->GetFileBuffer("eboot.bin");
28 if (buffer.empty()) {
29 // Try searching
30 auto files = lastPkg_->GetFileList();
31 for (const auto &f : files) {
32 if (f.find("eboot.bin") != std::string::npos ||
33 f.find(".elf") != std::string::npos) {
34 buffer = lastPkg_->GetFileBuffer(f);
35 break;
36 }
37 }
38 }
39 
40 if (!buffer.empty()) {
41 if (decompilerCtx_->LoadELF(buffer)) {
42 decompilerCtx_->Analyze();
43 } else {
44 decompilerCtx_->LoadBinary(buffer, 0x400000);
45 decompilerCtx_->Analyze();
46 }
47 
48 if (!decompilerCtx_->GetFunctions().empty()) {
49 selectedFunction_ = decompilerCtx_->GetFunctions()[0].get();
50 }
51 }
52 }
53 }
54 
55 ImGui::BeginGroup();
56 
57 // Left Panel: Function List
58 ImGui::BeginChild("FunctionList", ImVec2(250, 0), true);
59 ImGui::Text("Functions");
60 ImGui::Separator();
61 
62 for (const auto &func : decompilerCtx_->GetFunctions()) {
63 bool isSelected = (selectedFunction_ == func.get());
64 if (ImGui::Selectable(func->name.c_str(), isSelected)) {
65 selectedFunction_ = func.get();
66 }
67 }
68 
69 if (decompilerCtx_->GetFunctions().empty()) {
70 ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1.0), "No functions found.");
71 if (!lastPkg_) {
72 ImGui::TextWrapped("Load a PKG to start analysis.");
73 }
74 }
75 
76 // ┌─────────────────────────────────────────────────────────────────────────┐
77 // │ Progress Bar for Analysis │
78 // └─────────────────────────────────────────────────────────────────────────┘
79 const auto &progress = decompilerCtx_->GetProgress();
80 if (!progress.isComplete && !progress.currentPhase.empty()) {
81 ImGui::Separator();
82 ImGui::TextColored(ImVec4(0.3f, 0.8f, 1.0f, 1.0f), "Analysis Progress");
83 
84 if (progress.currentPhase == "scanning") {
85 ImGui::Text("Scanning for functions: %d prologues found",
86 progress.prologuesFound);
87 // Indeterminate progress (spinning)
88 float time = (float)ImGui::GetTime();
89 float progress_val = 0.5f + 0.5f * sinf(time * 3.0f);
90 ImGui::ProgressBar(progress_val, ImVec2(-1, 0), "Scanning...");
91 } else if (progress.currentPhase == "analyzing") {
92 ImGui::Text("Analyzing: %d / %d functions", progress.functionsAnalyzed,
93 progress.prologuesFound);
94 float fraction =
95 progress.prologuesFound > 0
96 ? (float)progress.functionsAnalyzed / progress.prologuesFound
97 : 0.0f;
98 ImGui::ProgressBar(fraction, ImVec2(-1, 0));
99 }
100 }
101 
102 ImGui::EndChild();
103 
104 ImGui::SameLine();
105 
106 // Right Panel: Code View
107 ImGui::BeginChild("CodeView", ImVec2(0, 0), true);
108 
109 if (selectedFunction_) {
110 ImGui::Text("Address: 0x%llX", selectedFunction_->address);
111 ImGui::SameLine();
112 ImGui::TextColored(
113 ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "[%s]",
114 selectedFunction_->category ==
115 ShadPKG::Decompiler::IR::Function::Category::GameLogic
116 ? "GameLogic"
117 : "Unknown");
118 ImGui::Separator();
119 
120 ImGui::BeginTabBar("CodeTabs");
121 if (ImGui::BeginTabItem("Disassembly")) {
122 ImGui::BeginChild("DisasmScroll");
123 for (const auto &bb : selectedFunction_->basicBlocks) {
124 ImGui::Spacing();
125 ImGui::TextColored(ImVec4(0.3f, 0.7f, 1.0f, 1.0f),
126 "[BLOCK 0x%llX - 0x%llX]", bb->startAddress,
127 bb->endAddress);
128 ImGui::Separator();
129 
130 for (const auto &instr : bb->instructions) {
131 ImGui::Text(" 0x%llX %s", instr.address, instr.disassembly.c_str());
132 if (ImGui::IsItemHovered()) {
133 ImGui::SetTooltip("Opcode: %d", (int)instr.opcode);
134 }
135 }
136 
137 if (!bb->successors.empty()) {
138 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
139 " -> Successors: ");
140 ImGui::SameLine();
141 for (auto succ : bb->successors) {
142 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "0x%llX ", succ);
143 ImGui::SameLine();
144 }
145 ImGui::NewLine();
146 }
147 }
148 ImGui::EndChild();
149 ImGui::EndTabItem();
150 }
151 if (ImGui::BeginTabItem("Pseudocode")) {
152 ImGui::BeginChild("PseudoScroll");
153 
154 // Check if we need to regenerate code
155 if (codeDirty_) {
156 // Trigger regeneration to update names/structure
157 decompilerCtx_->GenerateStructuredCode();
158 codeDirty_ = false;
159 }
160 
161 const auto &tokens =
162 decompilerCtx_->GetFunctionTokens(selectedFunction_->address);
163 
164 if (tokens.empty()) {
165 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
166 "// No code generated or function empty.");
167 } else {
168 bool firstOnLine = true;
169 for (const auto &token : tokens) {
170 // Determine Color
171 ImVec4 color(0.9f, 0.9f, 0.9f, 1.0f); // Default white
172 switch (token.type) {
173 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Keyword:
174 color = ImVec4(0.3f, 0.6f, 1.0f, 1.0f); // Blueish
175 break;
176 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Type:
177 color = ImVec4(0.3f, 0.8f, 0.6f, 1.0f); // Greenish-Cyan
178 break;
179 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Function:
180 color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // Yellow
181 break;
182 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Global:
183 color = ImVec4(0.8f, 0.5f, 1.0f, 1.0f); // Purple
184 break;
185 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::String:
186 color = ImVec4(1.0f, 0.6f, 0.3f, 1.0f); // Orange
187 break;
188 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Number:
189 color = ImVec4(0.6f, 1.0f, 0.6f, 1.0f); // Light Green
190 break;
191 case ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::Comment:
192 color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f); // Grey
193 break;
194 default:
195 break;
196 }
197 
198 // Handle Newlines explicitly if they are in text, or assume tokens
199 // flow The CppEmitter likely puts newlines as Text tokens or part of
200 // them. We'll iterate chars if necessary, but assuming clean tokens
201 // for now. If token text contains \n, we should probably print and
202 // reset line.
203 
204 // Interaction Check (before printing to capture cursor pos?)
205 // ImGui operates on the "last item". So we print, then check.
206 
207 if (!firstOnLine)
208 ImGui::SameLine(0, 0); // No extra spacing, let tokens control it
209 // (or add small spacing?)
210 // Actually, code tokens usually need spacing unless they are
211 // punctuation. CppEmitter should ideally include whitespace in tokens
212 // or we add it. For now, let's assume tokens include necessary
213 // whitespace or we play safe. BETTER: CppEmitter usually emits "int"
214 // then " " then "main". If not, we might squash things. Let's try 0
215 // spacing first.
216 
217 ImGui::TextColored(color, "%s", token.text.c_str());
218 
219 if (ImGui::IsItemHovered()) {
220 if (token.relatedAddress != 0) {
221 ImGui::SetTooltip("Address: 0x%llX", token.relatedAddress);
222 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
223 }
224 }
225 
226 // Click Navigation
227 if (token.relatedAddress != 0 && ImGui::IsItemClicked(0)) {
228 auto targetFunc =
229 decompilerCtx_->GetFunctionAt(token.relatedAddress);
230 if (targetFunc) {
231 selectedFunction_ = targetFunc.get();
232 }
233 }
234 
235 // Context Menu (Renaming & Retyping)
236 if (ImGui::IsItemClicked(1)) {
237 if (token.type ==
238 ShadPKG::Decompiler::Codegen::CppEmitter::TokenType::
239 Identifier) { // Assuming Identifier is used for vars
240 // Check if it matches a local variable
241 bool isLocal = false;
242 if (selectedFunction_) {
243 for (const auto &local : selectedFunction_->locals) {
244 if (local.name == token.text) {
245 isLocal = true;
246 break;
247 }
248 }
249 }
250 
251 if (isLocal) {
252 isRetyping_ = true;
253 variableToRetype_ = token.text;
254 retypeSearchBuffer_.resize(256, '\0');
255 ImGui::OpenPopup("RetypePopup");
256 }
257 } else if (token.relatedAddress != 0) {
258 renameAddress_ = token.relatedAddress;
259 // Pre-fill buffer with current name
260 std::string currentName =
261 decompilerCtx_->GetSymbolDatabase()->getSymbolName(
262 renameAddress_);
263 strncpy(renameBuffer_, currentName.c_str(),
264 sizeof(renameBuffer_) - 1);
265 ImGui::OpenPopup("RenameContextPopup");
266 }
267 }
268 
269 // Check for newline in text to reset firstOnLine
270 if (token.text.find('\n') != std::string::npos) {
271 firstOnLine = true;
272 } else {
273 firstOnLine = false;
274 }
275 }
276 }
277 
278 // Rename Popup
279 if (ImGui::BeginPopup("RenameContextPopup")) {
280 ImGui::Text("Rename Symbol at 0x%llX", renameAddress_);
281 if (ImGui::IsWindowAppearing())
282 ImGui::SetKeyboardFocusHere();
283 
284 if (ImGui::InputText("Name", renameBuffer_, sizeof(renameBuffer_),
285 ImGuiInputTextFlags_EnterReturnsTrue)) {
286 decompilerCtx_->GetSymbolDatabase()->renameSymbol(renameAddress_,
287 renameBuffer_);
288 codeDirty_ = true;
289 ImGui::CloseCurrentPopup();
290 }
291 if (ImGui::Button("Apply")) {
292 decompilerCtx_->GetSymbolDatabase()->renameSymbol(renameAddress_,
293 renameBuffer_);
294 codeDirty_ = true;
295 ImGui::CloseCurrentPopup();
296 }
297 ImGui::EndPopup();
298 }
299 
300 // Retype Popup
301 DrawRetypePopup();
302 
303 ImGui::EndChild();
304 ImGui::EndTabItem();
305 }
306 ImGui::EndTabBar();
307 
308 } else {
309 ImGui::Text("Select a function to view code.");
310 }
311 
312 ImGui::EndChild();
313 
314 ImGui::EndGroup();
315}
316 
317void DecompilerView::DrawRetypePopup() {
318 if (ImGui::BeginPopup("RetypePopup")) {
319 ImGui::Text("Retype Variable: %s", variableToRetype_.c_str());
320 
321 ImGui::Text("Search Type:");
322 ImGui::InputText("##search", &retypeSearchBuffer_[0], 256);
323 std::string search = retypeSearchBuffer_.c_str(); // Naive
324 
325 ImGui::Separator();
326 
327 ImGui::BeginChild("TypeList", ImVec2(300, 200), true);
328 
329 auto typeManager = decompilerCtx_->GetTypeManager();
330 auto allTypes = typeManager->getAllTypes();
331 
332 // Add Primitives manual or via GetAllTypes if it includes them?
333 // Assuming GetAllTypes includes logic or we add basic ones.
334 // If TypeManager only has Structs in 'namedTypes_', we need to ensure
335 // primitives are there. For now, let's list common primitives explicitly or
336 // assume TypeManager handles them.
337 
338 std::vector<std::string> typesToShow;
339 typesToShow.push_back("int8");
340 typesToShow.push_back("int16");
341 typesToShow.push_back("int32");
342 typesToShow.push_back("int64");
343 typesToShow.push_back("float");
344 typesToShow.push_back("double");
345 
346 for (const auto &[name, type] : allTypes) {
347 typesToShow.push_back(name);
348 }
349 
350 for (const auto &name : typesToShow) {
351 bool matches = search.empty() || name.find(search) != std::string::npos;
352 if (matches) {
353 if (ImGui::Selectable(name.c_str())) {
354 // APPLY TYPE
355 // Find local variable
356 for (auto &local : selectedFunction_->locals) {
357 if (local.name == variableToRetype_) {
358 // If user picked a struct, we need to know if it's Pointer or
359 // Value. GUI simplification: If existing type was pointer-like or
360 // var usage implies pointer, prefer pointer? Or show "T" and "T*"
361 // options.
362 
363 // Simple logic: Just set the type. If name matches a struct,
364 // create struct type. But wait, TypeManager returns a Type
365 // shared_ptr.
366 
367 auto type = typeManager->getType(name);
368 if (type) {
369 // Default to Pointer for Structs if not specified?
370 // User request: "Gestisci i puntatori: permetti all'utente di
371 // specificare se è T o T*" For now, defaulting to T* is safer
372 // for decompilation of objects.
373 
374 if (type->getKind() ==
375 ShadPKG::Decompiler::Analysis::Type::Kind::Struct) {
376 // Wrap in Pointer
377 local.complexType = std::make_shared<
378 ShadPKG::Decompiler::Analysis::PointerType>(type);
379 } else {
380 local.complexType = type;
381 }
382 codeDirty_ = true;
383 }
384 ImGui::CloseCurrentPopup();
385 break;
386 }
387 }
388 }
389 
390 // Add right-click for "As Pointer" vs "As Value" maybe?
391 if (ImGui::IsItemClicked(1)) {
392 // Advanced selection
393 }
394 }
395 }
396 
397 ImGui::EndChild();
398 ImGui::EndPopup();
399 }
400}
401 
402} // namespace ShadPKG::GUI
403