Migrate the C codebase to C++20 - #793
Open
carroarmato0 wants to merge 83 commits into
Open
Conversation
…oveRetro#762) * feat: full alpha transparency support for theme colors, 3-slicing for pill sprites * feat: option for a half-transparent top/bottom curtain on game switcher (helps when using transparent pills)
Reworked the nextui and minarch makefiles to build objects per-file and link with g++, which is what lets us mix C and C++ from here on. C files stay on gnu99, C++ files get gnu++20, and settings moves up to gnu++20 too. No behavior change yet - this just clears the way to convert files to C++ one at a time.
…ered_map Renamed nextui.c to nextui.cpp and got it building as C++20. The main change: the little Hash helper - a lightweight two-arrays-plus-linear-scan map that did the job fine at the sizes it was used for - is now a std::unordered_map, which we get for free now that this is a C++ TU. The rest was just making the existing C compile cleanly as C++: static_casts on the void* array reads, casts on malloc/Array_pop, const-correct label arrays, and named SDL_Rect temps instead of taking the address of a compound literal. Builds clean on desktop and behaves exactly like main - same behavior headless too, including a pre-existing font-under-dummy-driver crash that predates this change.
The tg5040 cross-compiler is still gcc 8.3, which knows -std=gnu++2a but not the newer -std=gnu++20 spelling, so hardcoding gnu++20 broke the device build. Probe the compiler at make time and use gnu++20 when it's available (desktop gcc, and the gcc 13/14 toolchain once we get there) or fall back to gnu++2a otherwise. Same language either way for what we use. Builds clean on desktop and cross-compiles for tg5040.
The background/thumbnail image loaders and the pill-animation worker use a hand-rolled multithreaded task queue with a latent memory-corruption bug: a stray write that smashes the stack (and even the dynamic linker's loaded-lib list). It was benign under the old C build but turns fatal once the file is compiled as C++ - the different codegen/layout shifts the write onto something that matters. It crashes a worker thread on game launch/exit; ASan masks it (timing/layout), which is how we cornered it. For the C++ port POC, gate those subsystems off behind NEXTUI_POC_NO_WORKERS: don't spawn the three worker threads, no-op the image loaders (folderbg/thumb surfaces stay NULL and the existing draw guards fall through to a clear), and set the selection pill's final position directly instead of queueing it for the animation worker. No thumbnails, box art, backgrounds or pill animation for now. Confirmed on-device (tg5040/Brick): boots, browses, launches and quits games cleanly - the crash is gone. These subsystems get reintroduced later with a proper thread-safe implementation.
The old IntArray was a fixed int[27] with an unchecked push - fine in practice since it only ever holds the A-Z+# alpha buckets, but a silent overflow waiting to happen if that assumption ever changed. std::vector drops the cap and the hand-rolled new/push/free. alphas stays a heap pointer for now so we don't have to touch how Directory is allocated.
Replaces the hand-rolled grow-by-doubling void** array with a std::vector under the hood. The public Array_* API and the typed wrappers (EntryArray/StringArray/DirectoryArray/RecentArray) stay exactly the same, so call sites only change ->items[i] to ->v[i] and ->count to ->count(). Semantics preserved on purpose: - Array_free stays container-only; the typed *Array_free wrappers keep freeing their elements first (mixing those up is a leak or a double free, so they're deliberately left untouched). - Array_yoink still moves the pointers over and frees only the source container. - Array_remove now no-ops on a missing item instead of walking off the end like the old while-loop did - strictly safer, same happy path. - EntryArray_sort keeps the same case-insensitive, not-stable ordering, now via std::sort instead of qsort.
Hash_set copies its value into a std::string now (since the Hash became an unordered_map), so the strdup() the two callers passed in was never freed - a small leak on every config/map line parsed. The strdup was a leftover from the old Hash that stored the raw pointer. Just pass the value straight through.
Swaps sprintf/strcpy for snprintf on every target that's a stack char array, so a long ROM name or path from the SD card truncates instead of smashing the 256-byte buffer. sizeof(buf) is provably right here since these targets are only ever declared as arrays. Also rewrote getUniqueName - it built '<name> (<tag>)' with a four-step strcpy-via-moving-pointer dance and computed a filename it never used. That's just one snprintf now. Left alone for a careful follow-up: the writes that target a pointer or a function param (cue_path, the tmp cursor, cmd, etc.) - sizeof would be the pointer size there, so those need their real buffer size worked out per caller.
Finishes the string-safety pass - nextui.cpp now has zero raw sprintf/strcpy/strcat. These were the trickier ones sizeof can't handle: - Function params (cue_path, m3u_path, disc_path): every caller passes a char[256], so they're bounded to 256 with a comment noting why. - The tmp write-cursor pattern (build a path prefix, then strcpy a readdir name or an extension onto the tail): bounded to the space actually left in the parent buffer, i.e. SIZE - (tmp - parent). The readdir-name appends were the real overflow risk - a long filename could run past the 256-byte path buffer. All behaviour-preserving for normal-length paths; a too-long path now truncates instead of corrupting the stack.
First of the void*->T* cleanups. The recents list is now a real std::vector<Recent*> instead of a void*-backed Array, so the 8 static_cast<Recent*> at the use sites are gone and the compiler knows the element type. Mechanical, behaviour-preserving: push_back/insert/pop_back/erase for the old Array_* calls, std::swap for the bump-to-top, and the typed RecentArray_indexOf/free helpers now take the vector by reference. The generic Array_* stay put - stack and entries still use them until their turn.
Second void*->T* cleanup. The nav stack (and pathToStack, which builds it) are now std::vector<Directory*>, dropping the 4 static_cast<Directory*>. pathToStack returns the vector by value; DirectoryArray_pop/free take it by reference. 'top' still just aliases the last element (stack.back()), so the pop-then-reassign ordering is unchanged.
Third void*->T* cleanup. The two local path lists (parent_paths in getCollections and last in loadLast) are std::vector<char*> now. Dropped the unused StringArray_indexOf; StringArray_free takes the vector by ref and frees each strdup'd string before clearing. Only Entry is left on the void*-backed Array now.
The last and biggest void*->T* cleanup, and it lets the hand-rolled Array type go away entirely. - Directory now holds its entries and alphas by value (std::vector), so Directory is allocated with new/delete instead of malloc/free. new Directory() value-initialises, so the scalar fields still start zeroed. - The nine get* builders (getRoot/getRoms/getEntries/getCollection/ getDiscs/getRecents/getCollections/getQuick*) return std::vector<Entry*> by value; quick/quickActions are by-value vectors too. - EntryArray_indexOf/sort/free take the vector by reference; the merge spots that used Array_yoink now splice with insert()+clear(). - Every static_cast<Entry*> is gone (58 casts removed across the whole Array->vector effort). With nothing left using it, the generic Array struct and its new/push/pop/unshift/remove/reverse/free/yoink are deleted. The whole crusty hand-rolled container layer is now std::vector.
Phase B kicks off common/core/ - a modern-C++ utility layer the other binaries can share (settings now, minarch once it's C++). core/str.h has copy/format/append that take the destination by reference to a fixed-size char array, so N is deduced at compile time. The nice part: you physically can't call them on a decayed char* - that's a compile error, not the silent 8-byte 'size' you'd get from snprintf(ptr, sizeof(ptr), ...). So they can't overflow and can't be mis-sized. There's a desktop self-test (tests/str_test.cpp) covering copy/format/append incl. truncation. Adopted them in nextui.cpp: the 63 snprintf(buf, sizeof(buf), ...) array sites became core::format(buf, ...) and the one bounded strncat became core::append. That it all compiles is itself a proof that every one of those targets really is a fixed array - if any were secretly a pointer, the template wouldn't compile. The pointer/cursor/param string writes stay as plain snprintf with their real runtime size. Header-only, so no makefile wiring - -I../common already covers it.
Starts the minarch robustness pass (Phase E), in plain C - no C++ conversion, since these files are emulation-critical and full of C-isms (designated inits etc). Same approach as the nextui string work: - Array and struct-field targets (game.path/name/alt_name/m3u_path, local path buffers) get snprintf(T, sizeof(T), ...); dropped the redundant (char*) casts on the game.* fields. - The tmp m3u-path cursor is bounded to the space left in m3u_path. The dangerous ones here were the filename copies - strcpy(game.name, strrchr(path,'/')+1) into a 128-byte field would overflow on a long ROM name. Now it truncates.
Config path/key/value buffers now snprintf with sizeof. The param and cursor cases handled explicitly: Config_getPath's filename is MAX_PATH at every caller; the default.cfg path cursors are bounded to the space left in their path buffer. The calloc(strlen+1)+strcpy dup became a plain strdup.
Finishes the minarch robustness pass - zero raw sprintf/strcpy/strcat left in the whole frontend. All in plain C (these stay .c for now). - Local + struct-field char arrays (game/core/menu fields, path buffers, cheat paths[][MAX_PATH]) -> snprintf with sizeof; const Core fields keep their (char*) cast. - Params bounded to the caller's real size: ma_saves filename is MAX_PATH; getAlias's alias is >=256 everywhere; Core_getName got an explicit out_size param (its one caller passes sizeof(core.name)) so the basename copy can't overflow core.name[128]. - The tmp path cursors are bounded to the space left in their parent. - The calloc(strlen+1)+strcpy dups became strdup - which also fixes a latent mis-size in ma_options where item->name was allocated for def->desc but copied getOptionNameFromKey()'s (possibly longer) result. Careful bit: an earlier auto-convert pass naively used sizeof() on struct-field targets, but field names like key/value are ambiguous across structs and some are char* (sizeof would be 8 -> truncation). Reverted that and classified every struct-field target by its real type.
First step of the data-driven device HAL, tg5040 only. The Brick and the Smart Pro were already 'two devices in one binary' - scattered is_brick?X:Y ternaries in ten macros (screen geometry, menu layout, a few joystick codes). Those are now two DeviceDescriptor instances, TG5040_BRICK and TG5040_SMART_PRO, selected at runtime by a single resolveDeviceModel() that replaces the four copy-pasted getenv+exactMatch blocks (there was even a maintainer TODO asking for exactly this). The geometry/button macros (FIXED_WIDTH, MAIN_ROW_COUNT, JOY_L3, ...) now read deviceModel->field. Safe because those macros were already runtime values (is_brick is a runtime global), so nothing used them as compile-time constants. deviceModel defaults to Smart Pro so the macros are valid before init, then resolveDeviceModel refines it. Behaviour-preserving. The PLAT_* facade is untouched, so nothing above the platform layer changes. This is the shape tg5050 slots into next - it becomes another descriptor rather than a forked platform.c. (Named deviceModel, not deviceInfo, to avoid colliding with settings.cpp's own DeviceInfo class.) Verified on Smart Pro: boots clean at 1280x720 (the non-brick descriptor).
Device HAL step 2. Grows DeviceDescriptor with the sysfs nodes that actually differ between platforms - the CPU-freq node, the GPU thermal zone, and the rumble GPIO. On tg5040 these are the same for Brick and Smart Pro (shared TG5040_* defines), but they're exactly where tg5050 diverges (cpu4 not cpu0, thermal_zone5 not zone2, gpio236 not gpio227), so they now live in the descriptor instead of as literals in the functions. PLAT_cpu_monitor and PLAT_setRumble read deviceModel->field. Only the values that genuinely vary went in - cpu_temp (thermal_zone0) is identical on both platforms, so it stays a literal (no point descriptor-izing a constant). The rumble *mechanism* difference (tg5040 voltage vs tg5050 level) is behavioural, not just a path, so that waits until the two platform.c bodies get unified. Behaviour-preserving on tg5040; verified on Brick (boots 1024x768, stable, rumble/perf paths unchanged).
api.h literally had a TODO over this: eight loose 'extern int's for the current render pass (currentshaderpass, currentshadersrcw/h, currentshaderdstw/h, currentshadertexw/h, should_rotate) shared between api.c, the platform GLES path (generic_video.c) and minarch (ma_video.c). Now they're one GFX_RenderState struct with a single extern, gfx_render, so the header exposes one well-named thing instead of eight. Fields renamed to the obvious short forms (pass, src_w, dst_h, ...). Purely a regrouping - same values, same cross-module writes (the struct fields stay writable), no logic touched in the GLES pipeline. api.h drops from 27 externs to 20. Verified on Brick: boots 1024x768, stable, renders fine. Note: generic_video.c is #include'd into platform.c, and make doesn't track that dep, so platform.o must be force-rebuilt (touch platform.c) after editing generic_video.c - else you get a stale-object link error.
First step of bringing the disabled visuals back, done right. The pill tween no longer runs in a worker thread. It used to: animWorker looped over frames computing the pill position, wrote it into a shared struct, handed that pointer to the main thread via a callback, and blocked on a condition variable until the next flip - with the struct written and read from two threads with no lock, and a queue-size counter decremented too late so the producer's drop-oldest ran on a stale count. That's the tangle that corrupted memory and crashed. A UI lerp doesn't need any of that. animPill now just records the start/target/frames, and advancePillAnim() - called once per main-loop iteration - steps the linear tween and sets pillRect, reproducing exactly what the old worker+callback did per frame (including hiding the text off-screen mid-slide). No thread, no queue, no cross-thread struct. The old animWorker/enqueue/animcallback/queue code is now dead; it'll be deleted in a follow-up so this commit stays focused. Image loading (backgrounds, thumbnails) is still gated off - that's next, on a proper thread-safe queue. Verified on Brick: boots, stable, no cores.
When frames<=1 (a directory change, where should_animate is false), the tween was still rendering the start position for one frame before snapping to the target - which flashed the pill at the previous directory's row (often near the top) on the way back out. Restore the instant behaviour the POC had for that case: jump straight to target, no start frame. The multi-frame slide (in-list navigation) is unchanged.
First of the image loaders returns, rebuilt right. Adds a reusable core/thread_safe_queue.h (std::mutex + condition_variable, blocking pop, drop-oldest when full) and runs the folder-background loader on one std::thread that pops requests off it. The old design was a hand-rolled SDL_mutex/SDL_cond linked-list queue whose size counter was decremented too late and whose task ownership crossed threads - that's the race the POC had to switch off. Now the correctness lives in one small tested-shaped place: the worker owns the request once it pops it, IMG_Load + SDL_ConvertSurfaceFormat only touch CPU-side pixels off the main thread, and the decoded surface is handed back through onBackgroundLoaded under bgMutex (the same mutex every main-thread draw of folderbgbmp already takes, so no torn read or use-after-free). shutdown() unblocks the worker's pop() and we join it before freeing anything. Capacity is 1: only the most recent folder's background matters; if you've already navigated on, the stale request is dropped before it loads. Thumbnails are still on the disabled path - next to migrate onto the same queue. Verified on Brick: boots, stable through navigation, no cores.
Same treatment as folder backgrounds: thumbnails now load on their own std::thread pulling from their own capacity-1 core::ThreadSafeQueue (separate from backgrounds so the two never evict each other's request). The worker checks the file exists, decodes off the main thread, and hands the surface to onThumbLoaded, which does the scaling + rounded-corner pass and stores thumbbmp under thumbMutex - the same mutex both main-thread draw sites already take. shutdown()+join on quit. Every image loader is now off the hand-rolled SDL queue and onto the tested-shaped ThreadSafeQueue; the whole NEXTUI_POC_NO_WORKERS subsystem is effectively re-enabled, rebuilt without the data race. (The dead legacy SDL queue/worker code is still in the file; a follow-up deletes it.) Verified on Brick: boots, stable through rapid navigation, no cores.
Two small fixes now that thumbnails are back: - When the highlighted entry has no box art, clear the thumbnail right away instead of queueing a load that only resolves to 'no art' a frame or two later. And only queue a load when the art file actually exists. - When the directory depth changes (moving up/down), clear the thumbnail surface and LAYER_THUMBNAIL *before* the directory transition animates. Going from a ROM folder up to the systems overview is gamelist->gamelist, so the existing 'clear when leaving a gamelist' guard didn't fire and the previous ROM's art flashed through the slide. previous_depth still holds the old depth at that point, so previous_depth != stack.size() detects the change. Verified on Brick: box art no longer lingers into the systems overview.
Now that folder backgrounds and thumbnails run on the ThreadSafeQueue + std::thread loaders and the pill animation is a plain main-thread tween, the old hand-rolled path is fully dead. This clears it out: - the SDL_mutex/SDL_cond linked-list task queues (TaskNode/AnimTaskNode, the head/tail globals, the per-queue mutexes and conds) plus their producers (enqueueBGTask/enqueueThumbTask/enqueueanmimtask) and consumers (BGLoadWorker/ThumbLoadWorker/animWorker) - animcallback + the finishedTask struct it handed back, and the frameReady/flipCond/frameMutex frame-flip handshake that fed animWorker - LoadBackgroundTask, the currentXQueueSize counters, MAX_QUEUE_SIZE, workerThreadsShutdown, and the SDL_Thread handles - the NEXTUI_POC_NO_WORKERS gate in init/cleanup and its scaffolding note AnimTask keeps only the fields animPill actually reads; init/cleanup now just create/destroy the four live mutexes (bg/thumb/anim/font) and spawn/join the two std::thread loaders. No behaviour change -- verified booting clean on the Brick with a stable launcher PID and no crash cores.
Brings the migration branch current with upstream (LoveRetro/NextUI). New upstream work pulled in: white-point colour correction + displaycal for tg5040 (LoveRetro#760), configurable font style and custom system fonts (LoveRetro#748/LoveRetro#729), usb-c card detection before boot (LoveRetro#750), the game-switcher label fix for entries without a save state (LoveRetro#751), readyResume on Quick Menu directory nav (LoveRetro#739). Two upstream commits (LoveRetro#733 cpu governor, LoveRetro#728 Rewind_init) are the same PRs our fork already carried under different SHAs; git merged them cleanly with no duplicated logic. Conflict resolution: - nextui.cpp: upstream still edits the old nextui.c, which we renamed to .cpp. Git followed the rename. The one real conflict was the recent-game preview button hints -- upstream's LoveRetro#751 changed the logic (group 0 always BACK; group 1 shows "A OPEN" when there's no save state instead of RESUME/REMOVE). Took upstream's logic, kept our C++-friendly cast style. - nextui.cpp: LoveRetro#739 added a readyResume call site into nextui.c using the old Array API (->count / ->items[]); rewrote it against std::vector to match the migrated container (top->entries[top->selected]). Also fixes a latent const-correctness bug the full clean build surfaced: the tg5040 device descriptor stores sysfs paths as const char*, but getInt/putInt/putFile took char* -- so libbatmondb (which compiles platform.c with -Werror=discarded-qualifiers) failed once its cached header forced a platform.c recompile. Those helpers only read the path string, so const-ifying the parameter is the correct fix. Verified: full clean build green for both desktop and tg5040 (nextui, minarch, settings).
Every button-hint group was passed as GFX_blitButtonGroup((char**)(const
char*[]){ ... }, ...). That GNU compound-literal argument is miscompiled by
the toolchain's g++ 8.3 -- the callee reads garbage, the pill computes as
zero-width, and nothing draws. Result: no on-screen hints (RESUME/OPEN/BACK
/SLEEP...) anywhere in the launcher.
Confirmed on device by probing one call two ways in the same frame:
named local array -> ow = 259 (correct)
compound literal -> ow = 0 (miscompiled)
The pattern came in with the C->C++ port: the original C used (char*[]){...}
(fine as C); it was cast to satisfy g++, which exposed the codegen bug. Route
the hints through a named local array instead, via a small BTN_HINTS() macro
(statement expression), and apply it to all 11 call sites. Same behaviour,
correct codegen -- hints are back, and the game-switcher RESUME/OPEN split by
save-state (from the upstream LoveRetro#751 merge) now shows correctly too.
Phase D nibble: both globals are used only inside api.c, yet were declared `extern` in api.h -- part of the god-object surface every module sees. Make them `static` and drop the externs. The LID_Context type stays public; only the instance goes private. Purely internal linkage -- no runtime change; the linker confirms nothing outside api.c referenced either symbol (all three binaries build clean).
ma_config.c -> ma_config.cpp (14 of ~16 minarch TUs now C++; only the big
minarch.c remains). Wrap project C headers in extern "C"; move to CXXSOURCE.
- 7 calloc/malloc/realloc results cast via (decltype(lhs)) (the variable is
in scope within its own initializer, so this works for the local decls too).
- device_button_names used sparse/out-of-order [BTN_ID_*]= array designators
(SELECT/START/Y/X/B/A listed against an enum ordered A,B,X,Y,START,SELECT);
g++ 8.3 rejects these as "non-trivial designated initializers". Rewritten
positional in exact BTN_ID enum order (0..15) so device_button_names[BTN_ID_X]
still resolves to the same string.
- The config global's .frontend/.core/.shaders.options = (Option[]){...} and
.shortcuts = (ButtonMapping[]){...} keep their [FE_OPT_*]/[SH_*]/[SHORTCUT_*]
designators: those are dense and in enum order, which g++ 8.3 accepts as
trivial. Their trailing bare {NULL} terminators (which mixed positional with
designated -> ill-formed in C++) are made designated: [SH_NONE] and
[SHORTCUT_COUNT]. Verified on-device that namespace-scope compound-literal
arrays get static storage on g++ 8.3 (data survives init, stays mutable), so
config.*.options is not left dangling.
Builds green on tg5040.
Renames minarch.c -> minarch.cpp and moves it from CSOURCE to CXXSOURCE.
minarch is now 100% C++; only shared common/ and RA C files remain in CSOURCE.
C++ conversion fixes:
- Wrap project headers (msettings.h, notification.h, ra_integration.h, all
ma_*.h incl. ma_runframe.h) in extern "C" so the definitions here and the
cross-TU calls resolve to the same unmangled symbols.
- Scope the post-Game_open body in main() in its own block: C++ forbids the
`goto finish` guard from jumping over the initialized locals
(has_pending_opt_change, rewind_initialized, pixels, rawSurface, converted)
that are otherwise in scope at the label.
- `struct Core core = {}`: Core has const char[] members, so C++ needs explicit
zero-init (matches C's static zero-init).
- Cast game.data (void*) to const uint8_t* for RA_loadGame.
Some libretro cores throw a C++ exception (Mednafen::MDFN_Error) instead of returning an error when handed a cheat code they can't parse -- e.g. RetroArch Game Genie format on the supafaust SNES core. Because libretro is a C ABI the frontend never guarded these calls, so the uncaught throw unwound back across the boundary into minarch and hit std::terminate -> the whole emulator aborted. (Pre-existing; the original all-C minarch aborted the same way. Now that minarch is C++ we can actually catch it.) Confirmed on-device: supafaust does not throw from retro_cheat_set -- it stores the code and defers parsing to retro_run(), so the throw escapes core.run() in the emulation loop, not the cheat-set call. - ma_runframe.cpp: wrap the run_frame() body so a core exception during emulation is caught. recoverFromCoreFault() clears the core's cheat state (so the next frame is clean instead of re-throwing), clears the frontend's enabled flags, and shows a one-time "Cheat rejected by core - cheats disabled" notification. - ma_core.cpp: defensively guard retro_cheat_set for cores that throw synchronously, and log when a core doesn't implement cheats at all. - ma_frontend_opts.cpp: the cheat menu's refresh loop skipped disabled cheats, leaving a stale "on" checkbox after auto-disable; mirror the data model unconditionally so the box reflects reality. Verified on tg5040 (Brick): SUPA/supafaust throws -> recovers with toast; PCE/ Beetle PCE doesn't throw -> unaffected; GB/gambatte applies cheats normally.
The Brick Pro is a tg5040 hybrid: a Brick chassis and 1024x768 screen with
the Smart Pro's dual analog thumbsticks. Detect it via the stock MainUI model
string (DEVICE=brickpro) and route it as a Brick-family device for everything
screen/LED/brightness/display-cal related, while giving it Smart Pro-style
analog-stick handling.
- utils.{c,h}: central isBrickModel() (brick + brickpro) replacing the
scattered exactMatch("brick", ...) checks in api.c, ledcontrol.c,
bootlogo.c, platform.c; msettings.c carries a local mirror (standalone .so).
- platform.c: new TG5040_BRICK_PRO descriptor; resolveDeviceModel() selects it.
- settings.cpp: new Model::BrickPro (appended); hasAnalogSticks() true for it;
Brick display-cal preset (shared screen).
- launch.sh / dbg/launch.sh / install/boot.sh: detect the Brick Pro, power the
L/R thumbstick GPIOs, and use the Brick LED channels + splash geometry.
Existing Brick/Smart Pro behaviour is unchanged (all additions are additive).
UNVERIFIED pending real hardware: the exact MainUI model string ("Trimui
Brick Pro" assumed) and the joystick button codes/stick enumeration.
Integrates the new upstream commits since c24b36d, adapted to the cpp20-migration branch's C++/DeviceDescriptor architecture. New upstream work brought in: - feat: Trimui Brick Pro support (LoveRetro#766) + fix: trigger LEDs not addressable (MAX_LIGHTS 4->5) - fix: unify color parsing (LoveRetro#774) -- also fixes a hex_str[9] overflow and missed alpha in colour presets - feat: configurable button hint styles (LoveRetro#763) - Update README license notice; Dependabot GitHub Actions bumps Brick Pro reconciliation (adopt upstream semantics): Our branch had its own Brick-family Brick Pro (commit 0a64688). Per decision, upstream's richer, distinct-device model is adopted and re-expressed through our DeviceDescriptor architecture: - is_brickpro is now separate from is_brick (no longer a Brick-family member); isBrickModel() helper removed. - TG5040_BRICK_PRO descriptor carries the new joy_l4=11, joy_r4=12, joy_menu_alt=15; L4/R4 trigger buttons wired through descriptor-driven JOY_L4/R4 macros and BTN_ID_L4/R4. - Five-zone LED layout (F1, F2, top bar, joysticks, rear triggers) with ledsettings_brickpro.txt and LED_PATH4; own brightness curve and displaycal preset via libmsettings. - platform.h keeps our descriptor macros (not upstream's is_brick|| is_brickpro ternaries); tg5050 descriptor gets explicit JOY_NA for the new fields. Feature ports kept our C++20 idioms: colorpickermenu/menu keep SurfacePtr RAII and GFX_getFonts() accessors while taking upstream's semantic fixes; button-hint input_rects added alongside our encapsulated font accessor. Duplicate BrickPro enumerator in settings.cpp resolved to upstream's. Upstream's re-enabled PLAT_pollInput debug LOG_info lines kept commented. Validated: full clean build on tg5040 (device target, incl. Brick Pro paths). Brick Pro remains hardware-unverified on both sides.
State_read/State_write in the non-HAS_SRM (#else) path declared `FILE *state_file = fopen(...)` after the early `goto error;` sites, so the error: label sat in scope of an initialization the goto jumped over -- ill-formed in C++. The HAS_SRM path was already hoisted (state_rzfile = NULL); the #else path was missed. The toolchain g++ 8.3 accepted it, so device builds passed, but the host g++ used for PLATFORM=desktop rejected it, breaking the desktop build at minarch. Hoist state_file to NULL before the first goto (as already done for state_rzfile) and assign the fopen() result later. Builds clean on tg5040 and desktop.
… fixes Ingests 6 upstream bugfix commits and adapts them to the C++20 branch: - fix: L4/R4 button mappings (LoveRetro#776) — device_button_names[] extended to 18 entries; kept as positional array (C++ rejects sparse designated initializers) with L4/R4 at enum indices 16/17. LOCAL_BUTTON_COUNT 16->18. - feat: minput now visualizes stick movements — upstream code referenced the now file-local `pad` global; routed through PAD_getContext() accessor. - fix: brick pro display calibration — BrickPro now uses its own DISPLAYCAL_PRESET_BRICKPRO (settings.cpp), matching the recalibrated displaycal.h preset instead of falling back to DISPLAYCAL_PRESET_BRICK. - fix: white point calibration resetting itself (msettings.c). - fix: PlayStation and cardinal X/Y symbol mapping; inputs.svg/PNG refresh. Conflicts resolved in ma_config.cpp and settings.cpp. Validated: tg5040 and desktop builds clean.
Two related fixes: 1. skeleton/EXTRAS/Emus/tg5050/SUPA.pak/default.cfg: change supafaust_thread_affinity_ppu from 0xc (CPUs 2+3, offline on tg5050) to 0x10 (CPU 4, online). Mirrors the upstream fix in PR LoveRetro#784. 2. workspace/all/minarch/ma_core.cpp: wrap retro_load_game in try/catch in Core_load(). A core that throws during load now logs the exception and exits cleanly instead of hitting std::terminate with no context. (C++ only; upstream's ma_core.c has no equivalent guard available.)
… pak fixes New upstream features merged: - Color palettes: 18 bundled palettes + user-loadable palette dir (LoveRetro#787) - FN1/FN2/HOME can now launch user-assigned tool paks (LoveRetro#788) - Keep device awake while connected as USB gadget (LoveRetro#783) - RetroAchievements identification for custom paks and multi-system emulators (LoveRetro#775) - Brick Pro rumble limited to 2.5V (was 3.3V, too strong) - Align Brick/Pro rumble strength Conflict resolutions (C++20 branch vs upstream): - nextui.cpp: kept RAII core::SurfacePtr, took upstream THEME_COLOR7 for preview fill - minput.c: kept GFX_getFonts() and pad-> accessors, took upstream button rendering (ALT_BUTTON_TEXT_COLOR, text_color local, GFX_blitAssetColor, THEME_COLOR2 for sticks) - tg5040 platform.c: accepted conditional MAX_VOLTAGE for Brick Pro; RUMBLE_PATH not needed (branch uses deviceModel->rumble_gpio_path from DeviceDescriptor)
- nextui.cpp: restore if(lastScreen==SCREEN_GAME) branch eaten by conflict resolution and update runFnAction() to use C++20 vector accessor (entries.size() / entries[i]) - api.c/h: restore ALT_BUTTON_TEXT_COLOR as public (non-static) with extern declaration so minput.c can reference it after upstream introduced its use there - platform.c: restore MAX_STRENGTH and MIN_VOLTAGE defines lost during conflict resolution
palette.c is only consumed by the settings component (C++); no C-only tool includes it, so there is no reason to keep it as a C translation unit. - palette.h: #pragma once, <cstdint>, drop <stdbool.h> (native bool in C++), struct tag instead of typedef struct - palette.cpp: <cstdio>/<cstring> instead of C headers; extern "C" wrapper around config.h (which has no __cplusplus guard) to preserve C linkage for CFG_* calls - settings/makefile: move from gcc SOURCE to g++ CXXSOURCE; drop palette.o from the intermediate-object mv and link list - palettemenu.cpp: move palette.h include out of the extern "C" block
Finishes minarch at 100% first-party C++: the two remaining C units move from CSOURCE to CXXSOURCE in the makefile (the RA-conditional block). Both sit on the rcheevos C hashing/callback boundary, so the conversion stays conservative — allocation remains malloc/free (non-throwing) and no STL containers are introduced on the callback path. - Add extern "C" guards to chd_reader.h and ra_integration.h so their symbols keep C linkage for the 5 already-C++ consumers. - Wrap the still-C project headers (config/http/api/notification/RA helpers) in extern "C" in each .cpp, matching the ma_environment.cpp pattern; keep system/SDL/rcheevos headers outside. - chd_reader: C++ headers, libchdr includes wrapped extern "C" defensively (cdrom.h is unguarded but only supplies macros/enums). - ra_integration: drop the redundant `volatile` on ra_response_queue_count (every access is under ra_queue_mutex, so the lock provides ordering; this also clears C++20's -Wvolatile deprecation on ++/--). Builds clean on tg5040 (g++ 8.3, gnu++2a) and desktop (host g++, gnu++20).
Phase 2 (leaf tools), clean group — no SDL/compound-literals/nested functions, so these are near-mechanical. Each makefile moves from a single gcc compile+link to the settings.cpp-style mixed build: shared common/ + platform stay C (compiled to objects with gcc), the tool's .cpp is C++20, everything links through g++ -lstdc++. Still-C project headers (defines/api/utils/config, batmondb/gametimedb, msettings) are wrapped extern "C" in each .cpp. Also modernized: C++ <cXXX> headers, NULL -> nullptr. Dropped the C-only -std=gnu99 from the shared CFLAGS (it lazily leaked into the g++ command line via CXXFLAGS += $(CFLAGS)); common C now compiles at the default std, matching settings/makefile. Verified: tg5040 build clean (0 errors/warnings); nextval also builds desktop. On-device (tg5040): nextval --help, gametimectl usage, and syncsettings all run and exit 0 with correct output. batmon is a daemon — build-verified, logic unchanged.
Phase 2 (leaf tools), UI group with SDL. Same settings.cpp-style mixed makefile
(common/ + platform stay C; tool .cpp is C++20, linked via g++ -lstdc++), C
project headers wrapped extern "C".
C→C++ fixes:
- (SDL_Rect){...} compound literals -> named local SDL_Rect (the nextui.cpp
convention; g++'s handling of address-of-compound-literal is unreliable).
- (char*[]){...} button-hint arrays -> named local const char* arrays, avoiding
the g++ 8.3 miscompile that drops the literal's storage.
- clock: the four GCC nested functions (blit/blitBar/blitNumber/validate, a
C-only extension) become lambdas capturing the enclosing state by reference.
- string-literal arrays -> const char*; realloc results cast (bootlogo);
ampm_w initialized.
Residual -Wwrite-strings warnings (string literal -> char* C-API params) are
left as-is: they are inherent to the still-C common headers and already present
in bulk across nextui.cpp (~545). Verified clean builds: bootlogo tg5040; clock
tg5040 + desktop (host g++, gnu++20).
Phase 2 (leaf tools), the SDL-heavy UI group. Same settings.cpp-style mixed
makefile (common/ + platform stay C, tool .cpp is C++20, linked via g++
-lstdc++); still-C project headers (defines/api/utils/sdl, batmondb/gametimedb,
msettings) wrapped extern "C".
C→C++ fixes:
- Every &(SDL_Rect){...} compound literal -> a named local SDL_Rect. g++ in
strict C++ rejects taking the address of a compound-literal temporary
("taking address of temporary"), so this is required, not cosmetic. By-value
compound literals (drawBatteryIcon, rect = ...) become SDL_Rect{...}.
- (char*[]){...} button-hint arrays -> named local const char* arrays (the
g++ 8.3 storage-drop miscompile).
- malloc/realloc results cast; NULL -> nullptr; C++ <cXXX> headers.
- ledcontrol: explicit (int) cast on color1 in the int settings_values[]
brace-init to make the uint32_t->int narrowing intentional (-Wnarrowing).
Verified: full `make build` clean on tg5040 (g++ 8.3, gnu++2a) and desktop
(host g++, gnu++20); all 10 tool elfs produced. minput also builds desktop.
Phase 3 (leaf libraries). These build .so's whose C ABI is consumed by the battery/batmon and gametime/gametimectl binaries, so the exports must stay C-linkable: batmondb.h / gametimedb.h gain #ifdef __cplusplus / extern "C" guards, which also keeps their existing consumers (that wrap the include in extern "C") working via harmless nested extern "C". - Convert .c -> .cpp; wrap the still-C common headers (defines/utils) in extern "C"; sqlite3.h and the guarded lib header stay outside. - Makefiles split into a gcc C compile (common utils/api/platform) + a g++ C++20 compile of the library unit, linked -shared through g++ -lstdc++. - The library compile keeps -Werror; the C++ unit adds -Wno-write-strings because the exports call common C helpers taking char* (exists/touch, sql string literals) — benign but an error under C++ -Werror. Modernized internals only (nullptr in batmondb, malloc cast in gametimedb); no ABI change. Verified: full `make build` clean on tg5040; both .so's rebuilt and all four consumer elfs relink. On-device, `gametimectl list` loads the new C++ libgametimedb.so and prints play activity correctly (exit 0).
Phase 4 (shared common/ core), first sub-batch — the lowest-fan-out RA helpers, consumed only by minarch and/or settings. Both consumers already link via g++, so this just moves the units from each makefile's C-compiled list to the C++-compiled list. The common files keep C linkage (platform.c is still C and calls into them): ra_sync.h / ra_event_queue.h were already extern "C"-guarded; added guards to ra_offline.h. Each .cpp wraps the still-C project headers it includes in extern "C" (ra_integration.cpp pattern). C++ fixes required by the stricter compiler: - ra_offline.cpp: two functions used `goto` to cleanup labels across local initializations; hoisted the crossed declarations above the jumps (the labels don't use those vars). Same class as the earlier ma_saves fix. - ra_event_queue.cpp: it forward-declares LOG_note locally (to avoid api.h's transitive deps); marked that declaration extern "C" so it resolves to api.c's C symbol instead of a mangled C++ one. Verified: full `make build` clean on tg5040 (g++ 8.3) and desktop (host g++); minarch + settings relink on both.
…++20
Phase 4 (shared common/ core), second sub-batch — the rest of the low-fan-out
RA/http/notification helpers (consumed only by minarch and/or settings). Added
extern "C" guards to their headers so exports stay C-linkable for the still-C
platform.c; each .cpp wraps the still-C project headers it includes in
extern "C". Moved the units to the C++ side of the minarch + settings makefiles.
Code fixes surfaced by C++:
- http.cpp: 9 malloc/calloc/realloc results explicitly cast to their target type.
- ra_auth.cpp: calloc result cast.
- notification.cpp: the one &(SDL_Rect){...} compound literal -> a named local.
Verified: full `make build` clean on tg5040 (g++ 8.3) and desktop (host g++);
minarch + settings relink on both.
Remaining in Phase 4: the four high-fan-out core files (utils, scaler, config,
api), each compiled into 11-14 binaries.
Phase 4 (shared common/ core), third batch — three of the four high-fan-out core files, done together so each consumer makefile is edited once. Converted across all 15 consumers (9 tools, minarch, settings, nextui, both libs, and the tg5040 poweroff_next platform tool). Only api.c remains in C. Kept C linkage throughout (platform.c is still C and calls these): added extern "C" guards to utils.h / config.h / scaler.h; scaler.cpp now includes its own header so its exports keep C linkage; each .cpp wraps still-C project headers in extern "C". C++ fixes the stricter compiler required: - utils.cpp: strrchr/strstr on const args return const char* in C++ — const- correct baseName's local and cast the replaceString2 traversal. - config.cpp: reordered the NextUISettings default initializer to declaration order (C++20 requires it), and — because g++ 8.3 rejects designated initializers for char-array members — moved the six char[] string fields out of the aggregate into strcpy() calls. - scaler.cpp: bare `restrict` -> `__restrict` (C++ has no `restrict` keyword); `(void*)p + n` void-pointer arithmetic -> `(uint16_t*)((char*)p + n)`. - poweroff_next: its makefile now compiles the tool as C and utils/config as C++20, linking through g++. Verified: full `make build` clean on tg5040 (g++ 8.3, gnu++2a) and desktop (host g++, gnu++20); nextui/minarch/settings/all tools/libs relink on both. Remaining in Phase 4: api.c (the last core file, ~4.7k lines w/ ~210 compound literals).
Phase 4 complete: api.c was the last high-fan-out core file (13 consumers,
~4.7k lines). Moved to the C++ side of all 13 makefiles (8 tools, minarch,
settings, nextui; the two libs don't link api so it's just dropped there).
The only remaining common/ .c files are generic_*/displaycal, which are
#included into platform.c and stay C with the platform layer.
Kept C linkage (platform.c is still C and calls GFX_*/PAD_*/etc.): added
extern "C" guards to api.h; api.cpp wraps the still-C project headers.
C++ fixes:
- 39 address-of-a-compound-literal sites &(SDL_Rect){...} -> named local rects
(g++ rejects taking the address of a temporary). The ~84 by-value asset-rect
compound literals stay as GNU compound-literal rvalues (no address, no
miscompile). Two `dst_rect = &(SDL_Rect){...}` defaults became function-scope
locals so the pointer doesn't dangle.
- 3 (char*[]){...} button-hint literals in GFX_blitHardwareHints -> named local
arrays (the g++ 8.3 storage-drop miscompile).
- Reordered the libsamplerate SRC_DATA designated init to declaration order.
- int -> enum casts nextui surfaced without -fpermissive (LightProfile); one
float->int narrowing cast in a progress-bar rect.
Verified: full `make build` clean on tg5040 (g++ 8.3) and desktop (host g++);
every binary (nextui, minarch, settings, all 10 tools) and both libs relink.
displaycal is the last shared common/ source still compiled as C. It is only built into tg5040's libmsettings.so. Compile it with g++ under -std=gnu++2a but with -fno-exceptions/-fno-rtti so the object references no libstdc++ symbols, and keep linking libmsettings.so with gcc — the .so's NEEDED graph is byte-identical (no libstdc++ injected into a lib loaded by every process, including the still-C daemons).
Prep for converting each platform's platform.c (which unity-includes these
shared generic_*.c files) to C++20. All fixes are valid C as well, so the
files keep compiling as C for platforms not yet converted:
- explicit casts on realloc/malloc/calloc void* results (generic_platform
joysticks; generic_video cleaned/pragmas/overlay_path/pixels)
- reorder FX_Context 'effect' designated initializer to declaration order
- replace two &(SDL_Rect){...} compound-literal pointers in PLAT_flip with
named SDL_Rect locals (address-of-temporary is ill-formed in C++)
No behavior change; tg5040 C build verified green.
Prep for renaming platform.c -> platform.cpp. Valid C as well (no behavior change), so the files keep building as C until the makefiles switch: - reorder DeviceDescriptor initializers (joy_l4/r4/menu_alt before joy_plus/minus; rumble_gpio_path before gpu_speed_fixed) and the WIFI_connection initializer to declaration order (C++ rejects out-of-order designators) - PLAT_getCurrentTimezone: return NULL not false on failure (char*, not bool -- harmless 0==NULL in C, a type error in C++) desktop/platform/platform.c already compiles clean as C++; no change.
Renames each platform's platform.c to platform.cpp and compiles it with g++. The four shared common/generic_*.c files are textual #include fragments of platform.cpp, so they now build as C++ too (no standalone C compile remains); they keep the .c extension as include-only fragments, per route (a) -- the GLES pipeline in generic_video.c is compile-clean but otherwise untouched. - 9 tool makefiles + settings: compile the platform TU with $(CXX)/$(CXXFLAGS) instead of $(CC)/$(CFLAGS) - nextui, minarch: move the platform TU from CSOURCE to CXXSOURCE (the %.cpp pattern rule + vpath already cover it) - libbatmondb, libgametimedb: drop the platform compile entirely -- that object was compiled but never linked into the .so (dead build step) - platform.cpp wraps <msettings.h> in extern "C" (a C library header without its own guards) so libmsettings' symbols resolve with C linkage Builds green on tg5040, tg5050, and desktop. On-device runtime validation across the three handhelds still pending.
msettings is the settings-persistence shared library loaded by every process. Compile it with g++ (-fno-exceptions/-fno-rtti, -Wno-write-strings for the benign string-literal->char* helper calls) but keep linking libmsettings.so with plain gcc, so its dependency graph is unchanged -- no libstdc++ injected into a lib loaded everywhere. Verified on all 3 platforms: NEEDED list identical and SetVolume/SetBrightness/... still exported unmangled (C linkage), so every consumer links. - wrap the TU's own <msettings.h> include in extern "C" so the definitions keep C linkage (else the .so would export mangled names and break the ABI) - (Settings*) cast on mmap() results (tg5040/tg5050) - explicit (unsigned long) cast to avoid a narrowing conversion in a braced initializer (tg5040 SetRawBrightness)
The last first-party C daemons. These are standalone binaries (not the shared libmsettings.so), so linking libstdc++ is fine and consistent with how the tools link. - keymon (tg5040/tg5050): compile with g++; wrap <msettings.h> in extern "C" so libmsettings' symbols resolve with C linkage - rfkill (tg5040): compile+link with g++ - poweroff_next (tg5040): move the tool TU from the C compile to the g++ one (the makefile already linked common/utils+config as C++ through g++) Builds green on tg5040 and tg5050.
Collaborator
Author
|
Looks like build-core (tg5050, fceumm) fails due to an upstream change, doesn't look like it has something to do with my changes |
The C++20 migration PR must not change behavior. Commit 8f873ec had slipped in a tg5050-specific crash workaround (supafaust_thread_affinity_ppu 0xc->0x10, the LoveRetro#785/LoveRetro#784 fix) that belongs in its own change, not this migration. Restore it to upstream's 0xc so the PR stays behavior-neutral.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This converts all of NextUI's own C sources over to C++20. It's purely a language and build change, there are no behavior changes and no new features. The goal is to get the C++ base in place so it can be built on later (RAII-based memory-safety cleanups and so on) without the fork drifting away from upstream.
Goal:
It's not an endgoal, but rather an enabler. Getting every first-party files compiling as clean C++ so the real wins (memory-safety, RAII, better structure) become possible without rewriting behavior first.
Once that's in place, we can look into making it object oriented, add abstraction for making it easier to port to other devices, cleanup, consistency and improved maintainability.
Just about everything first-party is covered: the shared common/ core (api, config, utils, scaler, http, notification, the RetroAchievements pieces, displaycal), nextui, all of minarch, settings, the tool paks, the two small db libs, the platform HAL (each platform.c became platform.cpp, along with the generic_* files that are #included into it), libmsettings, and the keymon/rfkill/poweroff_next daemons.
The conversion is done file by file: rename .c to .cpp and link everything through g++. The C ABI is kept intact — api.h, the PLAT_* layer and msettings.h stay behind extern "C" so libmsettings and friends still export unmangled symbols and nothing downstream had to change. The shared libraries are compiled with -fno-exceptions/-fno-rtti and still linked with gcc, so libmsettings.so doesn't pull in libstdc++ even though its source is C++ now.
It builds on the tg5040 cross-compiler (gcc 8.3, so the makefiles fall back to -std=gnu++2a where -std=gnu++20 isn't recognised), on tg5050, and on the native desktop target. I packaged a MinUI.zip and flashed it to all three devices (Brick, Smart Pro, Smart Pro S) — brightness and volume, display colours, wifi, launching a game, and sleep/power all check out.
Happy to adjust or split this up however works best for review.