/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "log/log.h" #include "display/display.h" #include #include #include #include #define LOG_DEBUG_PATH "/apps/Dusk/debug.log" #define LOG_ERROR_PATH "/apps/Dusk/error.log" static bool_t fatTried = false; static bool_t fatReady = false; static void logInitFAT(void) { if(fatTried) return; fatTried = true; fatReady = fatInitDefault(); } void logDebug(const char_t *message, ...) { va_list args; va_start(args, message); // Print to stdout va_list copy; va_copy(copy, args); vfprintf(stdout, message, copy); va_end(copy); fflush(stdout); // Print to file logInitFAT(); if(fatReady) { FILE *file = fopen(LOG_DEBUG_PATH, "a"); if(file) { va_copy(copy, args); vfprintf(file, message, copy); va_end(copy); fclose(file); } } va_end(args); } void logError(const char_t *message, ...) { va_list args; va_start(args, message); // Write to file before displaying on screen logInitFAT(); if(fatReady) { FILE *file = fopen(LOG_ERROR_PATH, "a"); if(file) { va_list copy; va_copy(copy, args); vfprintf(file, message, copy); va_end(copy); fclose(file); } } // Either create graphics, or hijack the displays' graphics. GXRModeObj *rmode = NULL; void *framebuffer; if(DISPLAY.frameBuffer[0]) { console_init( DISPLAY.frameBuffer[0], 20, 20, DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->xfbHeight, DISPLAY.screenMode->fbWidth * VI_DISPLAY_PIX_SZ ); } else { VIDEO_Init(); rmode = VIDEO_GetPreferredMode(NULL); framebuffer = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); console_init( framebuffer, 20, 20, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth*VI_DISPLAY_PIX_SZ ); VIDEO_Configure(rmode); VIDEO_SetNextFramebuffer(framebuffer); VIDEO_SetBlack(FALSE); VIDEO_Flush(); VIDEO_WaitVSync(); if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync(); } // Printf va_list copy; va_copy(copy, args); vprintf(message, copy); va_end(copy); va_end(args); // PAD_Init is idempotent — safe to call even if inputInit already called it, // and handles the case where the error occurred before inputInit ran. PAD_Init(); while(SYS_MainLoop()) { PAD_ScanPads(); // START button matches the RAGEQUIT bind — allows exit on GC controller // when there is no Wiimote HOME button available. if(PAD_ButtonsDown(0) & PAD_BUTTON_START) break; VIDEO_WaitVSync(); } // Exit cleanly to HBC regardless of engine state. engineDispose() is not // called here because the engine may be partially initialized. exit(0); }