A tool for deriving PKG packet encryption keys for ps4 written in c++
| 1 | /* |
| 2 | * ╔═══════════════════════════════════════════════════════════════════════════╗ |
| 3 | * ║ SDL BACKEND IMPLEMENTATION ║ |
| 4 | * ╠═══════════════════════════════════════════════════════════════════════════╣ |
| 5 | * ║ Window, rendering, and input using SDL2. ║ |
| 6 | * ╚═══════════════════════════════════════════════════════════════════════════╝ |
| 7 | */ |
| 8 | |
| 9 | #include "sdl_backend.h" |
| 10 | |
| 11 | #ifndef PS4_NO_SDL |
| 12 | #include <SDL2/SDL.h> |
| 13 | #include <iostream> |
| 14 | #include <chrono> |
| 15 | #include <thread> |
| 16 | |
| 17 | namespace PS4Emu { |
| 18 | namespace SDL { |
| 19 | |
| 20 | // ═══════════════════════════════════════════════════════════════════════════ |
| 21 | // GLOBAL STATE |
| 22 | // ═══════════════════════════════════════════════════════════════════════════ |
| 23 | |
| 24 | static SDL_Window* g_window = nullptr; |
| 25 | static SDL_Renderer* g_renderer = nullptr; |
| 26 | static bool g_initialized = false; |
| 27 | static bool g_shouldQuit = false; |
| 28 | static bool g_vsyncEnabled = true; |
| 29 | static PadState g_padState = {}; |
| 30 | static uint64_t g_initTime = 0; |
| 31 | |
| 32 | // Keyboard to PAD mapping |
| 33 | struct KeyMapping { |
| 34 | SDL_Scancode key; |
| 35 | uint32_t button; |
| 36 | }; |
| 37 | |
| 38 | static const KeyMapping g_keyMappings[] = { |
| 39 | { SDL_SCANCODE_RETURN, PAD_OPTIONS }, |
| 40 | { SDL_SCANCODE_UP, PAD_UP }, |
| 41 | { SDL_SCANCODE_DOWN, PAD_DOWN }, |
| 42 | { SDL_SCANCODE_LEFT, PAD_LEFT }, |
| 43 | { SDL_SCANCODE_RIGHT, PAD_RIGHT }, |
| 44 | { SDL_SCANCODE_Z, PAD_CROSS }, // X button |
| 45 | { SDL_SCANCODE_X, PAD_CIRCLE }, // O button |
| 46 | { SDL_SCANCODE_A, PAD_SQUARE }, // Square |
| 47 | { SDL_SCANCODE_S, PAD_TRIANGLE }, // Triangle |
| 48 | { SDL_SCANCODE_Q, PAD_L1 }, |
| 49 | { SDL_SCANCODE_W, PAD_R1 }, |
| 50 | { SDL_SCANCODE_E, PAD_L2 }, |
| 51 | { SDL_SCANCODE_R, PAD_R2 }, |
| 52 | { SDL_SCANCODE_1, PAD_L3 }, |
| 53 | { SDL_SCANCODE_2, PAD_R3 }, |
| 54 | { SDL_SCANCODE_SPACE, PAD_TOUCHPAD }, |
| 55 | }; |
| 56 | |
| 57 | // ═══════════════════════════════════════════════════════════════════════════ |
| 58 | // INITIALIZATION |
| 59 | // ═══════════════════════════════════════════════════════════════════════════ |
| 60 | |
| 61 | bool Initialize(const std::string& title, int width, int height) { |
| 62 | if (g_initialized) { |
| 63 | return true; |
| 64 | } |
| 65 | |
| 66 | std::cout << "[SDL] Initializing SDL2..." << std::endl; |
| 67 | |
| 68 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) < 0) { |
| 69 | std::cerr << "[SDL] Failed to initialize: " << SDL_GetError() << std::endl; |
| 70 | return false; |
| 71 | } |
| 72 | |
| 73 | // Create window |
| 74 | g_window = SDL_CreateWindow( |
| 75 | title.c_str(), |
| 76 | SDL_WINDOWPOS_CENTERED, |
| 77 | SDL_WINDOWPOS_CENTERED, |
| 78 | width, |
| 79 | height, |
| 80 | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE |
| 81 | ); |
| 82 | |
| 83 | if (!g_window) { |
| 84 | std::cerr << "[SDL] Failed to create window: " << SDL_GetError() << std::endl; |
| 85 | SDL_Quit(); |
| 86 | return false; |
| 87 | } |
| 88 | |
| 89 | // Create renderer with VSync |
| 90 | Uint32 rendererFlags = SDL_RENDERER_ACCELERATED; |
| 91 | if (g_vsyncEnabled) { |
| 92 | rendererFlags |= SDL_RENDERER_PRESENTVSYNC; |
| 93 | } |
| 94 | |
| 95 | g_renderer = SDL_CreateRenderer(g_window, -1, rendererFlags); |
| 96 | if (!g_renderer) { |
| 97 | std::cerr << "[SDL] Failed to create renderer: " << SDL_GetError() << std::endl; |
| 98 | SDL_DestroyWindow(g_window); |
| 99 | SDL_Quit(); |
| 100 | return false; |
| 101 | } |
| 102 | |
| 103 | // Initialize pad state |
| 104 | g_padState.connected = true; |
| 105 | g_padState.leftStickX = 128; |
| 106 | g_padState.leftStickY = 128; |
| 107 | g_padState.rightStickX = 128; |
| 108 | g_padState.rightStickY = 128; |
| 109 | g_padState.buttons = 0; |
| 110 | g_padState.l2 = 0; |
| 111 | g_padState.r2 = 0; |
| 112 | |
| 113 | g_initTime = SDL_GetTicks64(); |
| 114 | g_initialized = true; |
| 115 | g_shouldQuit = false; |
| 116 | |
| 117 | std::cout << "[SDL] Window created: " << width << "x" << height << std::endl; |
| 118 | std::cout << "[SDL] Controls: Arrow keys=D-Pad, Z=X, X=O, A=Square, S=Triangle" << std::endl; |
| 119 | std::cout << "[SDL] Q/W=L1/R1, E/R=L2/R2, Enter=Options, Space=Touchpad" << std::endl; |
| 120 | |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | void Shutdown() { |
| 125 | if (!g_initialized) return; |
| 126 | |
| 127 | std::cout << "[SDL] Shutting down..." << std::endl; |
| 128 | |
| 129 | if (g_renderer) { |
| 130 | SDL_DestroyRenderer(g_renderer); |
| 131 | g_renderer = nullptr; |
| 132 | } |
| 133 | |
| 134 | if (g_window) { |
| 135 | SDL_DestroyWindow(g_window); |
| 136 | g_window = nullptr; |
| 137 | } |
| 138 | |
| 139 | SDL_Quit(); |
| 140 | g_initialized = false; |
| 141 | } |
| 142 | |
| 143 | bool IsInitialized() { |
| 144 | return g_initialized; |
| 145 | } |
| 146 | |
| 147 | // ═══════════════════════════════════════════════════════════════════════════ |
| 148 | // WINDOW & RENDERING |
| 149 | // ═══════════════════════════════════════════════════════════════════════════ |
| 150 | |
| 151 | void* GetWindowHandle() { |
| 152 | return g_window; |
| 153 | } |
| 154 | |
| 155 | void* GetRendererHandle() { |
| 156 | return g_renderer; |
| 157 | } |
| 158 | |
| 159 | void* CreateFrameBuffer(int width, int height) { |
| 160 | if (!g_renderer) return nullptr; |
| 161 | |
| 162 | SDL_Texture* texture = SDL_CreateTexture( |
| 163 | g_renderer, |
| 164 | SDL_PIXELFORMAT_ARGB8888, |
| 165 | SDL_TEXTUREACCESS_STREAMING, |
| 166 | width, |
| 167 | height |
| 168 | ); |
| 169 | |
| 170 | return texture; |
| 171 | } |
| 172 | |
| 173 | void DestroyFrameBuffer(void* buffer) { |
| 174 | if (buffer) { |
| 175 | SDL_DestroyTexture(static_cast<SDL_Texture*>(buffer)); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | void UpdateFrameBuffer(void* buffer, const void* pixels, int pitch) { |
| 180 | if (!buffer || !pixels) return; |
| 181 | |
| 182 | SDL_UpdateTexture( |
| 183 | static_cast<SDL_Texture*>(buffer), |
| 184 | nullptr, |
| 185 | pixels, |
| 186 | pitch |
| 187 | ); |
| 188 | } |
| 189 | |
| 190 | void PresentFrameBuffer(void* buffer) { |
| 191 | if (!g_renderer || !buffer) return; |
| 192 | |
| 193 | SDL_RenderClear(g_renderer); |
| 194 | SDL_RenderCopy(g_renderer, static_cast<SDL_Texture*>(buffer), nullptr, nullptr); |
| 195 | SDL_RenderPresent(g_renderer); |
| 196 | } |
| 197 | |
| 198 | void ClearScreen(uint8_t r, uint8_t g, uint8_t b) { |
| 199 | if (!g_renderer) return; |
| 200 | SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); |
| 201 | SDL_RenderClear(g_renderer); |
| 202 | } |
| 203 | |
| 204 | void Present() { |
| 205 | if (!g_renderer) return; |
| 206 | SDL_RenderPresent(g_renderer); |
| 207 | } |
| 208 | |
| 209 | void SetVSync(bool enabled) { |
| 210 | g_vsyncEnabled = enabled; |
| 211 | // Note: VSync can only be changed by recreating the renderer |
| 212 | } |
| 213 | |
| 214 | // ═══════════════════════════════════════════════════════════════════════════ |
| 215 | // INPUT |
| 216 | // ═══════════════════════════════════════════════════════════════════════════ |
| 217 | |
| 218 | void PollEvents() { |
| 219 | SDL_Event event; |
| 220 | |
| 221 | while (SDL_PollEvent(&event)) { |
| 222 | switch (event.type) { |
| 223 | case SDL_QUIT: |
| 224 | g_shouldQuit = true; |
| 225 | break; |
| 226 | |
| 227 | case SDL_KEYDOWN: |
| 228 | case SDL_KEYUP: { |
| 229 | bool pressed = (event.type == SDL_KEYDOWN); |
| 230 | SDL_Scancode scancode = event.key.keysym.scancode; |
| 231 | |
| 232 | // Check key mappings |
| 233 | for (const auto& mapping : g_keyMappings) { |
| 234 | if (mapping.key == scancode) { |
| 235 | if (pressed) { |
| 236 | g_padState.buttons |= mapping.button; |
| 237 | } else { |
| 238 | g_padState.buttons &= ~mapping.button; |
| 239 | } |
| 240 | break; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // WASD for left stick |
| 245 | const uint8_t* keys = SDL_GetKeyboardState(nullptr); |
| 246 | |
| 247 | // Left stick (IJKL or WASD alternative) |
| 248 | g_padState.leftStickX = 128; |
| 249 | g_padState.leftStickY = 128; |
| 250 | if (keys[SDL_SCANCODE_I]) g_padState.leftStickY = 0; // Up |
| 251 | if (keys[SDL_SCANCODE_K]) g_padState.leftStickY = 255; // Down |
| 252 | if (keys[SDL_SCANCODE_J]) g_padState.leftStickX = 0; // Left |
| 253 | if (keys[SDL_SCANCODE_L]) g_padState.leftStickX = 255; // Right |
| 254 | |
| 255 | // Right stick (numpad or TFGH) |
| 256 | g_padState.rightStickX = 128; |
| 257 | g_padState.rightStickY = 128; |
| 258 | if (keys[SDL_SCANCODE_T]) g_padState.rightStickY = 0; |
| 259 | if (keys[SDL_SCANCODE_G]) g_padState.rightStickY = 255; |
| 260 | if (keys[SDL_SCANCODE_F]) g_padState.rightStickX = 0; |
| 261 | if (keys[SDL_SCANCODE_H]) g_padState.rightStickX = 255; |
| 262 | |
| 263 | // L2/R2 analog (E/R keys give full press) |
| 264 | g_padState.l2 = (g_padState.buttons & PAD_L2) ? 255 : 0; |
| 265 | g_padState.r2 = (g_padState.buttons & PAD_R2) ? 255 : 0; |
| 266 | |
| 267 | // ESC to quit |
| 268 | if (scancode == SDL_SCANCODE_ESCAPE && pressed) { |
| 269 | g_shouldQuit = true; |
| 270 | } |
| 271 | break; |
| 272 | } |
| 273 | |
| 274 | case SDL_WINDOWEVENT: |
| 275 | if (event.window.event == SDL_WINDOWEVENT_CLOSE) { |
| 276 | g_shouldQuit = true; |
| 277 | } |
| 278 | break; |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | bool ShouldQuit() { |
| 284 | return g_shouldQuit; |
| 285 | } |
| 286 | |
| 287 | PadState GetPadState() { |
| 288 | return g_padState; |
| 289 | } |
| 290 | |
| 291 | // ═══════════════════════════════════════════════════════════════════════════ |
| 292 | // TIMING |
| 293 | // ═══════════════════════════════════════════════════════════════════════════ |
| 294 | |
| 295 | uint64_t GetTicks() { |
| 296 | return SDL_GetTicks64() - g_initTime; |
| 297 | } |
| 298 | |
| 299 | void Delay(uint32_t ms) { |
| 300 | SDL_Delay(ms); |
| 301 | } |
| 302 | |
| 303 | void WaitVBlank() { |
| 304 | // If VSync is enabled, Present() already waits |
| 305 | // Otherwise, manually wait for ~16.67ms |
| 306 | if (!g_vsyncEnabled) { |
| 307 | static auto lastVBlank = std::chrono::steady_clock::now(); |
| 308 | auto now = std::chrono::steady_clock::now(); |
| 309 | auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(now - lastVBlank); |
| 310 | |
| 311 | const auto targetFrame = std::chrono::microseconds(16667); // 60 Hz |
| 312 | if (elapsed < targetFrame) { |
| 313 | std::this_thread::sleep_for(targetFrame - elapsed); |
| 314 | } |
| 315 | |
| 316 | lastVBlank = std::chrono::steady_clock::now(); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // ═══════════════════════════════════════════════════════════════════════════ |
| 321 | // TEXT AND DIALOG RENDERING |
| 322 | // ═══════════════════════════════════════════════════════════════════════════ |
| 323 | |
| 324 | static std::string g_dialogMessage = ""; |
| 325 | static std::string g_dialogTitle = ""; |
| 326 | |
| 327 | // Simple 8x8 bitmap font (subset of ASCII printable characters) |
| 328 | static const uint8_t g_font8x8[96][8] = { |
| 329 | {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // space |
| 330 | {0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00}, // ! |
| 331 | {0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00}, // " |
| 332 | {0x36,0x36,0x7F,0x36,0x7F,0x36,0x36,0x00}, // # |
| 333 | {0x0C,0x3E,0x03,0x1E,0x30,0x1F,0x0C,0x00}, // $ |
| 334 | {0x00,0x63,0x33,0x18,0x0C,0x66,0x63,0x00}, // % |
| 335 | {0x1C,0x36,0x1C,0x6E,0x3B,0x33,0x6E,0x00}, // & |
| 336 | {0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00}, // ' |
| 337 | {0x18,0x0C,0x06,0x06,0x06,0x0C,0x18,0x00}, // ( |
| 338 | {0x06,0x0C,0x18,0x18,0x18,0x0C,0x06,0x00}, // ) |
| 339 | {0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00}, // * |
| 340 | {0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00}, // + |
| 341 | {0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x06}, // , |
| 342 | {0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00}, // - |
| 343 | {0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00}, // . |
| 344 | {0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00}, // / |
| 345 | {0x3E,0x63,0x73,0x7B,0x6F,0x67,0x3E,0x00}, // 0 |
| 346 | {0x0C,0x0E,0x0C,0x0C,0x0C,0x0C,0x3F,0x00}, // 1 |
| 347 | {0x1E,0x33,0x30,0x1C,0x06,0x33,0x3F,0x00}, // 2 |
| 348 | {0x1E,0x33,0x30,0x1C,0x30,0x33,0x1E,0x00}, // 3 |
| 349 | {0x38,0x3C,0x36,0x33,0x7F,0x30,0x78,0x00}, // 4 |
| 350 | {0x3F,0x03,0x1F,0x30,0x30,0x33,0x1E,0x00}, // 5 |
| 351 | {0x1C,0x06,0x03,0x1F,0x33,0x33,0x1E,0x00}, // 6 |
| 352 | {0x3F,0x33,0x30,0x18,0x0C,0x0C,0x0C,0x00}, // 7 |
| 353 | {0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00}, // 8 |
| 354 | {0x1E,0x33,0x33,0x3E,0x30,0x18,0x0E,0x00}, // 9 |
| 355 | {0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x00}, // : |
| 356 | {0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x06}, // ; |
| 357 | {0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00}, // < |
| 358 | {0x00,0x00,0x3F,0x00,0x00,0x3F,0x00,0x00}, // = |
| 359 | {0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00}, // > |
| 360 | {0x1E,0x33,0x30,0x18,0x0C,0x00,0x0C,0x00}, // ? |
| 361 | {0x3E,0x63,0x7B,0x7B,0x7B,0x03,0x1E,0x00}, // @ |
| 362 | {0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00}, // A |
| 363 | {0x3F,0x66,0x66,0x3E,0x66,0x66,0x3F,0x00}, // B |
| 364 | {0x3C,0x66,0x03,0x03,0x03,0x66,0x3C,0x00}, // C |
| 365 | {0x1F,0x36,0x66,0x66,0x66,0x36,0x1F,0x00}, // D |
| 366 | {0x7F,0x46,0x16,0x1E,0x16,0x46,0x7F,0x00}, // E |
| 367 | {0x7F,0x46,0x16,0x1E,0x16,0x06,0x0F,0x00}, // F |
| 368 | {0x3C,0x66,0x03,0x03,0x73,0x66,0x7C,0x00}, // G |
| 369 | {0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00}, // H |
| 370 | {0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00}, // I |
| 371 | {0x78,0x30,0x30,0x30,0x33,0x33,0x1E,0x00}, // J |
| 372 | {0x67,0x66,0x36,0x1E,0x36,0x66,0x67,0x00}, // K |
| 373 | {0x0F,0x06,0x06,0x06,0x46,0x66,0x7F,0x00}, // L |
| 374 | {0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x00}, // M |
| 375 | {0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x00}, // N |
| 376 | {0x1C,0x36,0x63,0x63,0x63,0x36,0x1C,0x00}, // O |
| 377 | {0x3F,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00}, // P |
| 378 | {0x1E,0x33,0x33,0x33,0x3B,0x1E,0x38,0x00}, // Q |
| 379 | {0x3F,0x66,0x66,0x3E,0x36,0x66,0x67,0x00}, // R |
| 380 | {0x1E,0x33,0x07,0x0E,0x38,0x33,0x1E,0x00}, // S |
| 381 | {0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00}, // T |
| 382 | {0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00}, // U |
| 383 | {0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00}, // V |
| 384 | {0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00}, // W |
| 385 | {0x63,0x63,0x36,0x1C,0x1C,0x36,0x63,0x00}, // X |
| 386 | {0x33,0x33,0x33,0x1E,0x0C,0x0C,0x1E,0x00}, // Y |
| 387 | {0x7F,0x63,0x31,0x18,0x4C,0x66,0x7F,0x00}, // Z |
| 388 | {0x1E,0x06,0x06,0x06,0x06,0x06,0x1E,0x00}, // [ |
| 389 | {0x03,0x06,0x0C,0x18,0x30,0x60,0x40,0x00}, // backslash |
| 390 | {0x1E,0x18,0x18,0x18,0x18,0x18,0x1E,0x00}, // ] |
| 391 | {0x08,0x1C,0x36,0x63,0x00,0x00,0x00,0x00}, // ^ |
| 392 | {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF}, // _ |
| 393 | {0x0C,0x0C,0x18,0x00,0x00,0x00,0x00,0x00}, // ` |
| 394 | {0x00,0x00,0x1E,0x30,0x3E,0x33,0x6E,0x00}, // a |
| 395 | {0x07,0x06,0x06,0x3E,0x66,0x66,0x3B,0x00}, // b |
| 396 | {0x00,0x00,0x1E,0x33,0x03,0x33,0x1E,0x00}, // c |
| 397 | {0x38,0x30,0x30,0x3E,0x33,0x33,0x6E,0x00}, // d |
| 398 | {0x00,0x00,0x1E,0x33,0x3F,0x03,0x1E,0x00}, // e |
| 399 | {0x1C,0x36,0x06,0x0F,0x06,0x06,0x0F,0x00}, // f |
| 400 | {0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x1F}, // g |
| 401 | {0x07,0x06,0x36,0x6E,0x66,0x66,0x67,0x00}, // h |
| 402 | {0x0C,0x00,0x0E,0x0C,0x0C,0x0C,0x1E,0x00}, // i |
| 403 | {0x30,0x00,0x30,0x30,0x30,0x33,0x33,0x1E}, // j |
| 404 | {0x07,0x06,0x66,0x36,0x1E,0x36,0x67,0x00}, // k |
| 405 | {0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00}, // l |
| 406 | {0x00,0x00,0x33,0x7F,0x7F,0x6B,0x63,0x00}, // m |
| 407 | {0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x00}, // n |
| 408 | {0x00,0x00,0x1E,0x33,0x33,0x33,0x1E,0x00}, // o |
| 409 | {0x00,0x00,0x3B,0x66,0x66,0x3E,0x06,0x0F}, // p |
| 410 | {0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x78}, // q |
| 411 | {0x00,0x00,0x3B,0x6E,0x66,0x06,0x0F,0x00}, // r |
| 412 | {0x00,0x00,0x3E,0x03,0x1E,0x30,0x1F,0x00}, // s |
| 413 | {0x08,0x0C,0x3E,0x0C,0x0C,0x2C,0x18,0x00}, // t |
| 414 | {0x00,0x00,0x33,0x33,0x33,0x33,0x6E,0x00}, // u |
| 415 | {0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x00}, // v |
| 416 | {0x00,0x00,0x63,0x6B,0x7F,0x7F,0x36,0x00}, // w |
| 417 | {0x00,0x00,0x63,0x36,0x1C,0x36,0x63,0x00}, // x |
| 418 | {0x00,0x00,0x33,0x33,0x33,0x3E,0x30,0x1F}, // y |
| 419 | {0x00,0x00,0x3F,0x19,0x0C,0x26,0x3F,0x00}, // z |
| 420 | {0x38,0x0C,0x0C,0x07,0x0C,0x0C,0x38,0x00}, // { |
| 421 | {0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00}, // | |
| 422 | {0x07,0x0C,0x0C,0x38,0x0C,0x0C,0x07,0x00}, // } |
| 423 | {0x6E,0x3B,0x00,0x00,0x00,0x00,0x00,0x00}, // ~ |
| 424 | {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // DEL |
| 425 | }; |
| 426 | |
| 427 | void DrawText(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b) { |
| 428 | if (!g_renderer || !text) return; |
| 429 | |
| 430 | SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); |
| 431 | |
| 432 | int curX = x; |
| 433 | int curY = y; |
| 434 | int scale = 2; // 2x scale for visibility |
| 435 | |
| 436 | for (const char* p = text; *p; p++) { |
| 437 | if (*p == '\n') { |
| 438 | curX = x; |
| 439 | curY += 10 * scale; |
| 440 | continue; |
| 441 | } |
| 442 | |
| 443 | int charIndex = *p - 32; |
| 444 | if (charIndex < 0 || charIndex >= 96) charIndex = 0; |
| 445 | |
| 446 | const uint8_t* glyph = g_font8x8[charIndex]; |
| 447 | for (int row = 0; row < 8; row++) { |
| 448 | for (int col = 0; col < 8; col++) { |
| 449 | // Fixed: was (7 - col), now just col for correct orientation |
| 450 | if (glyph[row] & (1 << col)) { |
| 451 | SDL_Rect pixel = {curX + col * scale, curY + row * scale, scale, scale}; |
| 452 | SDL_RenderFillRect(g_renderer, &pixel); |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 | curX += 8 * scale; |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | void DrawRect(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b, bool filled) { |
| 461 | if (!g_renderer) return; |
| 462 | |
| 463 | SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); |
| 464 | SDL_Rect rect = {x, y, w, h}; |
| 465 | |
| 466 | if (filled) { |
| 467 | SDL_RenderFillRect(g_renderer, &rect); |
| 468 | } else { |
| 469 | SDL_RenderDrawRect(g_renderer, &rect); |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | void SetDialogMessage(const char* message) { |
| 474 | if (message) { |
| 475 | g_dialogMessage = message; |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | const char* GetCurrentDialogMessage() { |
| 480 | return g_dialogMessage.c_str(); |
| 481 | } |
| 482 | |
| 483 | void ShowDialog(const char* title, const char* message) { |
| 484 | if (!g_renderer) return; |
| 485 | |
| 486 | g_dialogTitle = title ? title : ""; |
| 487 | g_dialogMessage = message ? message : ""; |
| 488 | |
| 489 | // Draw dialog box |
| 490 | int winW = 800, winH = 600; |
| 491 | int boxW = 500, boxH = 200; |
| 492 | int boxX = (winW - boxW) / 2; |
| 493 | int boxY = (winH - boxH) / 2; |
| 494 | |
| 495 | // Background |
| 496 | DrawRect(boxX, boxY, boxW, boxH, 40, 40, 80, true); |
| 497 | // Border |
| 498 | DrawRect(boxX, boxY, boxW, boxH, 100, 100, 200, false); |
| 499 | DrawRect(boxX+2, boxY+2, boxW-4, boxH-4, 80, 80, 160, false); |
| 500 | |
| 501 | // Title bar |
| 502 | DrawRect(boxX, boxY, boxW, 30, 60, 60, 120, true); |
| 503 | DrawText(title, boxX + 10, boxY + 8, 255, 255, 255); |
| 504 | |
| 505 | // Message |
| 506 | DrawText(message, boxX + 20, boxY + 50, 220, 220, 255); |
| 507 | |
| 508 | // OK button hint |
| 509 | DrawText("Press X to continue", boxX + 150, boxY + boxH - 35, 150, 150, 200); |
| 510 | } |
| 511 | |
| 512 | } // namespace SDL |
| 513 | } // namespace PS4Emu |
| 514 | |
| 515 | #else // PS4_NO_SDL - provide no-op stubs |
| 516 | |
| 517 | #include <iostream> |
| 518 | |
| 519 | namespace PS4Emu { |
| 520 | namespace SDL { |
| 521 | |
| 522 | bool Initialize(const std::string& title, int width, int height) { |
| 523 | std::cout << "[SDL] No-SDL stub: Initialize(\"" << title << "\")" << std::endl; |
| 524 | return true; |
| 525 | } |
| 526 | void Shutdown() {} |
| 527 | bool IsInitialized() { return false; } |
| 528 | void* GetWindowHandle() { return nullptr; } |
| 529 | void* GetRendererHandle() { return nullptr; } |
| 530 | void* CreateFrameBuffer(int, int) { return nullptr; } |
| 531 | void DestroyFrameBuffer(void*) {} |
| 532 | void UpdateFrameBuffer(void*, const void*, int) {} |
| 533 | void PresentFrameBuffer(void*) {} |
| 534 | void ClearScreen(uint8_t, uint8_t, uint8_t) {} |
| 535 | void Present() {} |
| 536 | void SetVSync(bool) {} |
| 537 | void DrawText(const char*, int, int, uint8_t, uint8_t, uint8_t) {} |
| 538 | void DrawRect(int, int, int, int, uint8_t, uint8_t, uint8_t, bool) {} |
| 539 | void ShowDialog(const char* title, const char* message) { |
| 540 | std::cout << "[DIALOG] " << (title ? title : "") << ": " << (message ? message : "") << std::endl; |
| 541 | } |
| 542 | void SetDialogMessage(const char*) {} |
| 543 | const char* GetCurrentDialogMessage() { return ""; } |
| 544 | void PollEvents() {} |
| 545 | bool ShouldQuit() { return false; } |
| 546 | PadState GetPadState() { return PadState{}; } |
| 547 | uint64_t GetTicks() { return 0; } |
| 548 | void Delay(uint32_t) {} |
| 549 | void WaitVBlank() {} |
| 550 | |
| 551 | } // namespace SDL |
| 552 | } // namespace PS4Emu |
| 553 | |
| 554 | #endif // PS4_NO_SDL |
| 555 |