@@ -19,6 +19,8 @@ extern void elogf(const char* fmt, ...);
1919#include < SDL2/SDL.h>
2020#include < whb/proc.h>
2121#include < proc_ui/procui.h>
22+ #include < coreinit/thread.h>
23+ #include < coreinit/time.h>
2224#include < SDL2/SDL_ttf.h>
2325#include < SDL2/SDL_image.h>
2426
@@ -32,6 +34,11 @@ extern "C" {
3234App::App () = default;
3335
3436App::~App () {
37+ // Standard SDL2-on-Wii-U teardown order. SDL_Quit() internally drives
38+ // SYSLaunchMenu + ProcUIProcessMessages drain + ProcUIShutdown via its
39+ // WIIU_VideoQuit handler (because we let SDL_Init claim ProcUI in
40+ // main() by NOT calling WHBProcInit). No custom ProcUI bookkeeping
41+ // needed on our side.
3542 elog (" ~App: DownloadQueue stop" );
3643 DownloadQueue::get ().stop ();
3744 elog (" ~App: clearing screens" );
@@ -48,7 +55,7 @@ App::~App() {
4855 IMG_Quit ();
4956 elog (" ~App: TTF_Quit" );
5057 TTF_Quit ();
51- elog (" ~App: SDL_Quit" );
58+ elog (" ~App: SDL_Quit (drains ProcUI internally) " );
5259 SDL_Quit ();
5360 elog (" ~App: done" );
5461}
@@ -60,10 +67,16 @@ bool App::init() {
6067 // Without these calls, Cemu's emulated socket layer never finishes
6168 // setup and curl connect() hangs. The previous BUILD_HW gate broke
6269 // cemu mode entirely (app stuck on "Loading repos...").
63- nn::ac::Initialize ();
64- nn::ac::Connect ();
70+ {
71+ nn::Result r = nn::ac::Initialize ();
72+ elogf (" Network: nn::ac::Initialize -> %s" , r.IsSuccess () ? " ok" : " FAILED" );
73+ }
74+ {
75+ nn::Result r = nn::ac::Connect ();
76+ elogf (" Network: nn::ac::Connect -> %s" , r.IsSuccess () ? " ok" : " FAILED" );
77+ }
6578 socket_lib_init ();
66- elog (" Network ready" );
79+ elog (" Network: socket_lib_init done, network ready" );
6780#endif
6881
6982 if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) != 0 ) {
@@ -130,7 +143,11 @@ bool App::init() {
130143 elog (" mkdir done" );
131144 Logger::get ().init (Paths::modstoreBase () + " /app.log" );
132145 elog (" logger init done" );
133- LOG_INFO (" App started" );
146+ #ifdef APP_VERSION
147+ LOG_INFO (" App started (CoffeeShop %s)" , APP_VERSION );
148+ #else
149+ LOG_INFO (" App started (no version define)" );
150+ #endif
134151 elog (" before pushScreen" );
135152 pushScreen (std::make_unique<MainLayout>(this ));
136153 elog (" after pushScreen" );
@@ -164,62 +181,67 @@ void App::run() {
164181 m_running = true ;
165182 int pruneCounter = 0 ;
166183 int frameNum = 0 ;
167- elogf (" App::run entering -- screens=%zu running=%d procRunning=%d" ,
168- m_screens.size (), (int )m_running,
169- #ifdef __WUT__
170- (int )WHBProcIsRunning ()
171- #else
172- 1
173- #endif
174- );
184+ elogf (" App::run entering -- screens=%zu running=%d" , m_screens.size (), (int )m_running);
185+ // NOTE: We deliberately do NOT call WHBProcIsRunning() here -- it would
186+ // run ProcUIProcessMessages and then ProcUIShutdown() because WHB's
187+ // sRunning flag is false (we never called WHBProcInit). That would tear
188+ // down ProcUI before we ever rendered a frame. SDL drives ProcUI for
189+ // us inside SDL_PollEvent below.
175190
176- while (m_running && !m_screens.empty () && WHBProcIsRunning ()) {
177- if (frameNum < 10 || frameNum % 60 == 0 ) {
178- elogf (" frame %d begin" , frameNum);
179- }
191+ // SDL2-wuhb owns the ProcUI lifecycle (we don't call WHBProcInit, so
192+ // SDL_Init claims it via ProcUIInitEx and sets handleProcUI=TRUE on
193+ // its side). SDL drives ProcUI from WIIU_PumpEvents (called by
194+ // SDL_PollEvent below) and fires SDL_QUIT when EXITING is received.
195+ // We must NOT use WHBProcIsRunning() as a loop guard: WHB's sRunning
196+ // flag is false because we never called WHBProcInit, and the first
197+ // WHBProcIsRunning() call would call ProcUIShutdown() and return
198+ // false, exiting the loop with 0 frames. Use SDL_QUIT (set via
199+ // m_running=false) as the canonical exit trigger instead.
200+ while (m_running && !m_screens.empty ()) {
201+ // Log only the first 10 frames to confirm the loop started cleanly.
202+ // Periodic per-second pulses were diagnostic for the HOME-exit hang
203+ // and are no longer needed; they'd just bloat early.log.
204+ if (frameNum < 10 ) elogf (" frame %d begin" , frameNum);
180205 SDL_Event event;
181206 while (SDL_PollEvent (&event)) {
182207 if (event.type == SDL_QUIT ) {
183208 elog (" got SDL_QUIT" );
184209 m_running = false ;
185210 }
186211 }
187- if (frameNum < 5 ) elog (" before update" );
188212 update ();
189- if (frameNum < 5 ) elog (" after update" );
190213 render ();
191- if (frameNum < 5 ) elogf (" after render (sdl_err='%s')" , SDL_GetError ());
192214 TextCache::get ().tick ();
193215 if (++pruneCounter > 300 ) {
194216 TextCache::get ().prune ();
195217 pruneCounter = 0 ;
196- elogf (" frame %d: pruned text cache" , frameNum);
197218 }
198219 frameNum++;
199220 }
200- elogf (" App::run loop exited -- frames=%d running=%d screens=%zu procRunning=%d" ,
201- frameNum, (int )m_running, m_screens.size (),
202- #ifdef __WUT__
203- (int )WHBProcIsRunning ()
204- #else
205- 1
206- #endif
207- );
208- #ifdef __WUT__
209- // Drain ProcUI until Aroma confirms exit - required for WHBProcShutdown to work
210- while (WHBProcIsRunning ()) {}
211- #endif
221+ elogf (" App::run loop exited -- frames=%d running=%d screens=%zu exiting=%d" ,
222+ frameNum, (int )m_running, m_screens.size (), (int )m_exiting);
223+ // No ProcUI drain here. SDL_Quit's WIIU_VideoQuit handles the exit
224+ // transition: it calls SYSLaunchMenu, then loops on
225+ // ProcUIProcessMessages(TRUE) handling RELEASE_FOREGROUND and exiting
226+ // when EXITING is received, then calls ProcUIShutdown. We just need
227+ // to return from run() so main() can let the App destructor (and
228+ // therefore SDL_Quit) run.
212229}
213230
214231void App::quit () {
232+ elog (" App::quit called -- setting m_running=false (no SYSLaunchMenu)" );
215233 m_running = false ;
216234}
217235void App::startExit () {
236+ // Just stop the main loop. SDL2-wuhb is the single owner of the
237+ // ProcUI/SYSLaunchMenu handshake: SDL_Quit's WIIU_VideoQuit detects
238+ // !exitingProcUI and calls SYSLaunchMenu + drains ProcUI itself.
239+ // Calling SYSLaunchMenu here too would issue it twice in flight
240+ // (once now, once during SDL_Quit) and reproduce the original
241+ // "Wii U Menu loading" hang. See Codex review 2026-05-..
242+ elog (" App::startExit called -- m_running=false, SDL_Quit will own SYSLaunchMenu" );
218243 m_exiting = true ;
219244 m_running = false ;
220- #ifdef __WUT__
221- SYSLaunchMenu ();
222- #endif
223245}
224246
225247void App::pushScreen (std::unique_ptr<Screen> screen) {
@@ -228,11 +250,15 @@ void App::pushScreen(std::unique_ptr<Screen> screen) {
228250}
229251
230252void App::popScreen () {
253+ elogf (" App::popScreen -- stack=%zu before" , m_screens.size ());
231254 if (!m_screens.empty ()) {
232255 m_screens.back ()->onExit ();
233256 m_screens.pop_back ();
234257 }
235- if (m_screens.empty ()) m_running = false ;
258+ if (m_screens.empty ()) {
259+ elog (" App::popScreen -- stack empty, setting m_running=false (no SYSLaunchMenu)" );
260+ m_running = false ;
261+ }
236262}
237263
238264void App::update () {
0 commit comments