Seregon/ShadPKG

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

C++/47.3 KB/No license
gui/StyleManager.cpp
ShadPKG / gui / StyleManager.cpp
1// SPDX-FileCopyrightText: Copyright 2025 shadPKG
2// SPDX-License-Identifier: GPL-2.0-or-later
3 
4#include "include/StyleManager.h"
5#include "include/FontAwesomeSolid900.h"
6#include "include/GuiLogSink.h"
7#include "include/IconsFontAwesome6.h"
8#include <assert.h>
9#include <cstring>
10#include <filesystem>
11#include <string>
12#include <vector>
13 
14namespace ShadPKG::GUI {
15 
16// ╔═══════════════════════════════════════════════════════════════════════════╗
17// ║ Font Storage ║
18// ╚═══════════════════════════════════════════════════════════════════════════╝
19static ImFont *g_DefaultFont = nullptr;
20static ImFont *g_MonospaceFont = nullptr;
21 
22ImFont *GetDefaultFont() { return g_DefaultFont; }
23ImFont *GetMonospaceFont() { return g_MonospaceFont; }
24ImFont *GetIconFont() { return g_DefaultFont; } // Icons merged with default
25 
26// ┌─────────────────────────────────────────────────────────────────────────┐
27// │ SetupImGuiStyle: Apply Cyber-Dark theme to ImGui │
28// └─────────────────────────────────────────────────────────────────────────┘
29void SetupImGuiStyle() {
30 ImGuiStyle &style = ImGui::GetStyle();
31 
32 // ═══════════════════════════════════════════════════════════════════════
33 // Geometry: Soft, modern rounded corners
34 // ═══════════════════════════════════════════════════════════════════════
35 style.WindowRounding = 6.0f;
36 style.ChildRounding = 6.0f;
37 style.FrameRounding = 4.0f;
38 style.PopupRounding = 4.0f;
39 style.ScrollbarRounding = 9.0f;
40 style.GrabRounding = 3.0f;
41 style.TabRounding = 4.0f;
42 
43 // Sizing
44 style.WindowPadding = ImVec2(12.0f, 12.0f);
45 style.FramePadding = ImVec2(8.0f, 4.0f);
46 style.ItemSpacing = ImVec2(8.0f, 6.0f);
47 style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
48 style.ScrollbarSize = 12.0f;
49 style.GrabMinSize = 10.0f;
50 style.WindowBorderSize = 1.0f;
51 style.FrameBorderSize = 0.0f;
52 style.AntiAliasedLines = true;
53 style.AntiAliasedLinesUseTex = true;
54 style.AntiAliasedFill = true;
55 
56 // ═══════════════════════════════════════════════════════════════════════
57 // Colors: Cyber-Dark Palette
58 // ═══════════════════════════════════════════════════════════════════════
59 ImVec4 *colors = style.Colors;
60 
61 // Base backgrounds
62 colors[ImGuiCol_WindowBg] = Colors::WindowBg;
63 colors[ImGuiCol_ChildBg] = Colors::ChildBg;
64 colors[ImGuiCol_PopupBg] = Colors::PopupBg;
65 colors[ImGuiCol_Border] = Colors::Border;
66 colors[ImGuiCol_BorderShadow] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
67 
68 // Text
69 colors[ImGuiCol_Text] = Colors::Text;
70 colors[ImGuiCol_TextDisabled] = Colors::TextDim;
71 
72 // Frame backgrounds (inputs, sliders, etc.)
73 colors[ImGuiCol_FrameBg] = Colors::FrameBg;
74 colors[ImGuiCol_FrameBgHovered] = Colors::FrameBgHover;
75 colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.28f, 0.28f, 1.0f);
76 
77 // Title bar
78 colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.09f, 0.09f, 1.0f);
79 colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.12f, 1.0f);
80 colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.09f, 0.09f, 0.09f, 0.5f);
81 
82 // Menu bar
83 colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
84 
85 // Scrollbar
86 colors[ImGuiCol_ScrollbarBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.5f);
87 colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.0f);
88 colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.0f);
89 colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.0f);
90 
91 // Checkbox & Radio
92 colors[ImGuiCol_CheckMark] = Colors::Primary;
93 
94 // Slider
95 colors[ImGuiCol_SliderGrab] = Colors::Primary;
96 colors[ImGuiCol_SliderGrabActive] = Colors::PrimaryHover;
97 
98 // Buttons
99 colors[ImGuiCol_Button] = Colors::FrameBg;
100 colors[ImGuiCol_ButtonHovered] = Colors::Primary;
101 colors[ImGuiCol_ButtonActive] = Colors::PrimaryActive;
102 
103 // Headers (collapsing headers, menu items)
104 colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
105 colors[ImGuiCol_HeaderHovered] = ImVec4(0.30f, 0.30f, 0.30f, 1.0f);
106 colors[ImGuiCol_HeaderActive] = ImVec4(0.0f, 0.68f, 0.71f, 0.4f);
107 
108 // Separator
109 colors[ImGuiCol_Separator] = Colors::Border;
110 colors[ImGuiCol_SeparatorHovered] = Colors::Primary;
111 colors[ImGuiCol_SeparatorActive] = Colors::PrimaryActive;
112 
113 // Resize grip
114 colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.26f, 0.26f, 0.5f);
115 colors[ImGuiCol_ResizeGripHovered] = Colors::Primary;
116 colors[ImGuiCol_ResizeGripActive] = Colors::PrimaryActive;
117 
118 // Tabs
119 colors[ImGuiCol_Tab] =
120 ImVec4(0.16f, 0.16f, 0.16f, 1.0f); // FIXED type error here too
121 colors[ImGuiCol_TabHovered] = Colors::Primary;
122 colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
123 colors[ImGuiCol_TabUnfocused] = ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
124 colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.18f, 0.18f, 0.18f, 1.0f);
125 
126 // Docking
127 colors[ImGuiCol_DockingPreview] =
128 ImVec4(Colors::Primary.x, Colors::Primary.y, Colors::Primary.z, 0.7f);
129 colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.0f);
130 
131 // Plot
132 colors[ImGuiCol_PlotLines] = Colors::Primary;
133 colors[ImGuiCol_PlotLinesHovered] = Colors::PrimaryHover;
134 colors[ImGuiCol_PlotHistogram] = Colors::Primary;
135 colors[ImGuiCol_PlotHistogramHovered] = Colors::PrimaryHover;
136 
137 // Table
138 colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.0f);
139 colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.31f, 1.0f);
140 colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.23f, 1.0f);
141 colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
142 colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.03f);
143 
144 // Text selection
145 colors[ImGuiCol_TextSelectedBg] =
146 ImVec4(Colors::Primary.x, Colors::Primary.y, Colors::Primary.z, 0.35f);
147 
148 // Drag and drop
149 colors[ImGuiCol_DragDropTarget] = Colors::Primary;
150 
151 // Navigation highlight
152 colors[ImGuiCol_NavHighlight] = Colors::Primary;
153 colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 1.0f, 1.0f, 0.70f);
154 colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
155 
156 // Modal window dim
157 colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.60f);
158}
159 
160// ┌─────────────────────────────────────────────────────────────────────────┐
161// │ LoadFonts: Load Roboto, JetBrainsMono, and merge FontAwesome │
162// └─────────────────────────────────────────────────────────────────────────┘
163static void AppendUnique(std::vector<std::filesystem::path> &paths,
164 const std::filesystem::path &path) {
165 if (path.empty())
166 return;
167 for (const auto &existing : paths) {
168 if (existing == path)
169 return;
170 }
171 paths.push_back(path);
172}
173 
174static std::vector<std::filesystem::path>
175FindFontsDirs(const std::vector<std::filesystem::path> &searchRoots) {
176 std::error_code ec;
177 std::vector<std::filesystem::path> results;
178 for (const auto &root : searchRoots) {
179 if (root.empty()) {
180 continue;
181 }
182 const auto candidate = root / "resources" / "fonts";
183 if (std::filesystem::exists(candidate, ec) &&
184 std::filesystem::is_directory(candidate, ec)) {
185 AppendUnique(results, candidate);
186 }
187 }
188 return results;
189}
190 
191static bool FileUsable(const std::filesystem::path &path) {
192 std::error_code ec;
193 if (!std::filesystem::exists(path, ec) ||
194 !std::filesystem::is_regular_file(path, ec)) {
195 return false;
196 }
197 const auto size = std::filesystem::file_size(path, ec);
198 return !ec && size > 0;
199}
200 
201bool LoadFonts(ImGuiIO &io, const std::filesystem::path &executableDir,
202 float baseFontSize) {
203 io.Fonts->Clear();
204 if (baseFontSize <= 0.0f) {
205 baseFontSize = 16.0f;
206 }
207 
208 std::error_code ec;
209 const auto currentPath = std::filesystem::current_path(ec);
210 std::vector<std::filesystem::path> searchRoots;
211 if (!executableDir.empty()) {
212 searchRoots.push_back(executableDir);
213 }
214 if (!ec && !currentPath.empty()) {
215 searchRoots.push_back(currentPath);
216 searchRoots.push_back(currentPath / "build");
217 searchRoots.push_back(currentPath.parent_path());
218 }
219 
220 if (!executableDir.empty()) {
221 searchRoots.push_back(executableDir.parent_path());
222 }
223 
224 std::vector<std::filesystem::path> fontsDirs = FindFontsDirs(searchRoots);
225 if (!ec && !currentPath.empty()) {
226 const auto repoCandidate = currentPath / "gui" / "resources" / "fonts";
227 if (std::filesystem::exists(repoCandidate, ec) &&
228 std::filesystem::is_directory(repoCandidate, ec)) {
229 AppendUnique(fontsDirs, repoCandidate);
230 }
231 }
232 
233 ImFont *defaultFont = nullptr;
234 ImFont *monospaceFont = nullptr;
235 ImFontConfig fontConfig;
236 fontConfig.OversampleH = 3;
237 fontConfig.OversampleV = 3;
238 fontConfig.PixelSnapH = true;
239 
240 for (const auto &fontsDir : fontsDirs) {
241 if (defaultFont)
242 break;
243 const auto robotoPath = fontsDir / "Roboto-Medium.ttf";
244 if (FileUsable(robotoPath)) {
245 defaultFont = io.Fonts->AddFontFromFileTTF(robotoPath.string().c_str(),
246 baseFontSize, &fontConfig);
247 }
248 }
249 
250 if (!defaultFont) {
251 defaultFont = io.Fonts->AddFontDefault(&fontConfig);
252 }
253 
254 if (defaultFont) {
255 // ┌─────────────────────────────────────────────────────────────────────┐
256 // │ FontAwesome 6 splits icons across TWO Unicode ranges: │
257 // │ Range 1: 0xE000-0xE6FF (new FA6 icons) │
258 // │ Range 2: 0xF000-0xF8FF (legacy FA icons) │
259 // │ Both ranges must be specified for all icons to display correctly │
260 // └─────────────────────────────────────────────────────────────────────┘
261 static const ImWchar iconRanges[] = {
262 0xE000, 0xE6FF, // FA6 new icons range
263 0xF000, 0xF8FF, // FA legacy icons range
264 0 // Null terminator (required by ImGui)
265 };
266 
267 // Load icons at same size as base font
268 float iconFontSize = baseFontSize;
269 if (iconFontSize <= 0.0f)
270 iconFontSize = 16.0f;
271 
272 bool iconsLoaded = false;
273 
274 // ┌─────────────────────────────────────────────────────────────────────┐
275 // │ Load FontAwesome icons MERGED into the default font │
276 // │ Using clear/reload approach which ensures proper glyph merging │
277 // └─────────────────────────────────────────────────────────────────────┘
278 if (font_awesome_solid_900_ttf_len > 0) {
279 // First pass: trigger font parsing by loading as separate font
280 ImFontConfig initConfig;
281 initConfig.MergeMode = false;
282 initConfig.PixelSnapH = true;
283 initConfig.FontDataOwnedByAtlas = false;
284 
285 io.Fonts->AddFontFromMemoryTTF(
286 (void *)font_awesome_solid_900_ttf,
287 static_cast<int>(font_awesome_solid_900_ttf_len), iconFontSize,
288 &initConfig, iconRanges);
289 
290 // Clear and reload properly
291 io.Fonts->Clear();
292 
293 // Reload default font
294 ImFontConfig fontConfig2;
295 fontConfig2.OversampleH = 3;
296 fontConfig2.OversampleV = 3;
297 fontConfig2.PixelSnapH = true;
298 
299 for (const auto &fontsDir : fontsDirs) {
300 const auto robotoPath = fontsDir / "Roboto-Medium.ttf";
301 if (FileUsable(robotoPath)) {
302 defaultFont = io.Fonts->AddFontFromFileTTF(
303 robotoPath.string().c_str(), baseFontSize, &fontConfig2);
304 if (defaultFont)
305 break;
306 }
307 }
308 if (!defaultFont) {
309 defaultFont = io.Fonts->AddFontDefault(&fontConfig2);
310 }
311 
312 // Now load icon font MERGED with proper config
313 ImFontConfig iconsConfig;
314 iconsConfig.MergeMode = true;
315 iconsConfig.PixelSnapH = true;
316 iconsConfig.OversampleH = 3; // High quality anti-aliasing
317 iconsConfig.OversampleV = 3;
318 iconsConfig.GlyphMinAdvanceX = 22.0f; // Slightly wider for 20px icons
319 iconsConfig.GlyphOffset = ImVec2(0.0f, 1.0f); // Re-center for 20px
320 iconsConfig.FontDataOwnedByAtlas = false;
321 
322 // Load icons LARGER than text for better visibility and definition
323 float highResIconSize = 20.0f;
324 
325 ImFont *iconsFont = io.Fonts->AddFontFromMemoryTTF(
326 (void *)font_awesome_solid_900_ttf,
327 static_cast<int>(font_awesome_solid_900_ttf_len), highResIconSize,
328 &iconsConfig, iconRanges);
329 
330 if (iconsFont) {
331 iconsLoaded = true;
332 GuiLogSink::Instance().Info("FontAwesome Icons loaded (Embedded)");
333 } else {
334 GuiLogSink::Instance().Error("Failed to load FontAwesome Icons");
335 }
336 }
337 
338 // Fallback to file if memory load failed (unlikely)
339 if (!iconsLoaded) {
340 for (const auto &fontsDir : fontsDirs) {
341 const auto iconsPath = fontsDir / "fa-solid-900.ttf";
342 if (!FileUsable(iconsPath))
343 continue;
344 
345 ImFontConfig fileFontConfig;
346 fileFontConfig.MergeMode = true;
347 fileFontConfig.PixelSnapH = true;
348 fileFontConfig.GlyphMinAdvanceX = iconFontSize;
349 fileFontConfig.GlyphOffset = ImVec2(0.0f, 3.0f);
350 
351 ImFont *iconsFont = io.Fonts->AddFontFromFileTTF(
352 iconsPath.string().c_str(), iconFontSize, &fileFontConfig,
353 iconRanges);
354 
355 if (iconsFont) {
356 iconsLoaded = true;
357 GuiLogSink::Instance().Info("FontAwesome Icons loaded from file: " +
358 iconsPath.string());
359 break;
360 }
361 }
362 }
363 
364 if (!iconsLoaded) {
365 GuiLogSink::Instance().Error("Could not load FontAwesome icons "
366 "(embedding failed and file not found)");
367 }
368 }
369 
370 // Load Monospace font (after potential atlas clear)
371 for (const auto &fontsDir : fontsDirs) {
372 if (monospaceFont)
373 break;
374 const auto monoPath = fontsDir / "JetBrainsMono-Regular.ttf";
375 if (FileUsable(monoPath)) {
376 monospaceFont = io.Fonts->AddFontFromFileTTF(
377 monoPath.string().c_str(), baseFontSize - 2.0f, &fontConfig);
378 }
379 }
380 
381 if (!monospaceFont) {
382 monospaceFont = defaultFont;
383 }
384 
385 g_DefaultFont = defaultFont;
386 g_MonospaceFont = monospaceFont;
387 io.FontDefault = g_DefaultFont;
388 
389 const bool built = io.Fonts->Build();
390 
391 // ┌───────────────────────────────────────────────────────────────────────┐
392 // │ Post-build: Count loaded icon glyphs for diagnostic info │
393 // └───────────────────────────────────────────────────────────────────────┘
394 if (built && g_DefaultFont) {
395 int glyphsFoundE = 0, glyphsFoundF = 0;
396 
397 // Count glyphs in Range 1: FA6 new icons (0xE000 - 0xE6FF)
398 for (ImWchar c = 0xE000; c <= 0xE6FF; c++) {
399 if (g_DefaultFont->FindGlyphNoFallback(c)) {
400 glyphsFoundE++;
401 }
402 }
403 // Count glyphs in Range 2: FA legacy icons (0xF000 - 0xF8FF)
404 for (ImWchar c = 0xF000; c <= 0xF8FF; c++) {
405 if (g_DefaultFont->FindGlyphNoFallback(c)) {
406 glyphsFoundF++;
407 }
408 }
409 }
410 
411 return g_DefaultFont != nullptr && built;
412}
413 
414bool LoadFonts(ImGuiIO &io, float baseFontSize) {
415 return LoadFonts(io, std::filesystem::path(), baseFontSize);
416}
417 
418} // namespace ShadPKG::GUI
419