diff --git a/App/ui/multiboot.c b/App/ui/multiboot.c index eb8580df..c270c72e 100644 --- a/App/ui/multiboot.c +++ b/App/ui/multiboot.c @@ -41,52 +41,32 @@ static void mb_copy_label(char *dst, uint8_t cap, const char *src, uint8_t src_c dst[n] = 0; } -static void mb_format_slot_label(char *dst, uint8_t cap, const mb_slot_header_t *header) +static uint8_t mb_copy_slot_version(char *dst, uint8_t cap, const mb_slot_header_t *header) { - const char *version = NULL; - uint8_t version_cap = 0; - uint8_t version_len = 0; - uint8_t name_limit = cap - 1u; uint8_t n = 0; - if (!header->name[0]) - { - mb_copy_label(dst, cap, header->fw_version, MB_VERSION_LEN); - return; - } - + dst[0] = 0; for (uint8_t i = 0; i + 1u < MB_VERSION_LEN && header->fw_version[i]; i++) { if (header->fw_version[i] == 'v' && header->fw_version[i + 1u] >= '0' && header->fw_version[i + 1u] <= '9') { - version = &header->fw_version[i + 1u]; - version_cap = MB_VERSION_LEN - i - 1u; + i++; + while (n + 1u < cap && i < MB_VERSION_LEN) + { + const char c = header->fw_version[i]; + if ((c < '0' || c > '9') && c != '.') + break; + dst[n++] = c; + i++; + } break; } } - if (version) - { - while (version_len < version_cap && version[version_len]) - version_len++; - if (version_len + 1u < cap) - name_limit = cap - version_len - 2u; - } - - while (n < name_limit && n < MB_NAME_LEN && header->name[n]) - { - dst[n] = header->name[n]; - n++; - } - if (version && n + version_len + 1u < cap) - { - dst[n++] = ' '; - for (uint8_t i = 0; i < version_len; i++) - dst[n++] = version[i]; - } dst[n] = 0; + return n; } /* "MULTIBOOT" mode banner in the top status bar, shown on every screen - the @@ -116,6 +96,17 @@ static void mb_key_hints(const char *act_menu, const char *act_exit) GUI_DisplaySmallest(act_exit, (uint8_t)(xe + 16u + sp), 49, false, true); } +static void mb_invert_rounded_row(uint8_t line) +{ + gFrameBuffer[line][0] ^= 0x7Fu; + for (uint8_t x = 1u; x < LCD_WIDTH - 1u; x++) + { + gFrameBuffer[line][x] ^= 0xFFu; + gFrameBuffer[line - 1u][x] ^= 0x80u; + } + gFrameBuffer[line][LCD_WIDTH - 1u] ^= 0x7Fu; +} + static void mb_show_message(const char *line1, const char *line2, const char *line3) { UI_DisplayClear(); @@ -171,6 +162,7 @@ static void mb_render_slots(uint8_t selected, const uint8_t status[MB_SLOT_COUNT]) { char line[19]; /* 18 glyphs max: 18 * 7 px fits from x=2 to x=126. */ + char version[MB_VERSION_LEN]; UI_DisplayClear(); mb_status_bar(); @@ -178,23 +170,48 @@ static void mb_render_slots(uint8_t selected, for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++) { const uint8_t fbLine = (uint8_t)(slot + 1u); /* page 1 stays blank */ + uint8_t version_len = 0; + uint8_t version_x = 0; memset(line, 0, sizeof(line)); + memset(version, 0, sizeof(version)); line[0] = (char)('0' + slot); line[1] = ' '; line[2] = ' '; if (status[slot] == MB_OK) - mb_format_slot_label(&line[3], sizeof(line) - 3u, &headers[slot]); + { + uint8_t name_cap = sizeof(line) - 3u; + + version_len = mb_copy_slot_version(version, sizeof(version), &headers[slot]); + if (version_len) + { + const uint8_t name_x = 2u + 3u * 7u; + uint8_t available; + + version_x = (uint8_t)(LCD_WIDTH - 2u - version_len * 7u); + available = version_x > name_x + ? (uint8_t)((version_x - name_x) / 7u) + : 0u; + if (available + 1u < name_cap) + name_cap = available + 1u; + } + + if (headers[slot].name[0]) + mb_copy_label(&line[3], name_cap, headers[slot].name, MB_NAME_LEN); + else if (!version_len) + mb_copy_label(&line[3], name_cap, headers[slot].fw_version, MB_VERSION_LEN); + } else mb_copy_label(&line[3], sizeof(line) - 3u, mb_error_text(status[slot]), 20u); UI_PrintStringSmallNormal(line, 2, 0, fbLine); + if (version_len) + UI_PrintStringSmallNormal(version, version_x, 0, fbLine); - /* Selected row: full-width inverse bar, like the firmware menu list. */ + /* Selected row: full-width rounded inverse capsule. */ if (slot == selected) - for (uint8_t x = 0; x < LCD_WIDTH; x++) - gFrameBuffer[fbLine][x] ^= 0xFFu; + mb_invert_rounded_row(fbLine); } mb_key_hints("SELECT", "QUIT"); diff --git a/CMakePresets.json b/CMakePresets.json index f8e8a7bc..08968372 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -99,99 +99,6 @@ "TARGET": "f4hwn.custom" } }, - { - "name": "Bandscope", - "inherits": "default", - "cacheVariables": { - "ENABLE_SPECTRUM": true, - "ENABLE_FMRADIO": false, - "ENABLE_VOX": false, - "ENABLE_AIRCOPY": true, - "ENABLE_FEAT_F4HWN_K5VIEWER": true, - "ENABLE_FEAT_F4HWN_GAME": false, - "ENABLE_FEAT_F4HWN_PMR": true, - "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": false, - "EDITION_STRING": "Bandscope", - "TARGET": "f4hwn.bandscope" - } - }, - { - "name": "Broadcast", - "inherits": "default", - "cacheVariables": { - "ENABLE_SPECTRUM": false, - "ENABLE_FMRADIO": true, - "ENABLE_VOX": true, - "ENABLE_AIRCOPY": true, - "ENABLE_FEAT_F4HWN_K5VIEWER": true, - "ENABLE_FEAT_F4HWN_GAME": false, - "ENABLE_FEAT_F4HWN_PMR": true, - "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": false, - "EDITION_STRING": "Broadcast", - "TARGET": "f4hwn.broadcast" - } - }, - { - "name": "Basic", - "inherits": "default", - "cacheVariables": { - "ENABLE_SPECTRUM": true, - "ENABLE_FMRADIO": true, - "ENABLE_VOX": false, - "ENABLE_AIRCOPY": false, - "ENABLE_FEAT_F4HWN_K5VIEWER": false, - "ENABLE_FEAT_F4HWN_GAME": false, - "ENABLE_FEAT_F4HWN_SPECTRUM": false, - "ENABLE_FEAT_F4HWN_PMR": true, - "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_AUDIO_BAR": false, - "ENABLE_FEAT_F4HWN_RESUME_STATE": false, - "ENABLE_FEAT_F4HWN_CHARGING_C": false, - "ENABLE_FEAT_F4HWN_INV": true, - "ENABLE_FEAT_F4HWN_CTR": false, - "ENABLE_FEAT_F4HWN_NARROWER": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": false, - "EDITION_STRING": "Basic", - "TARGET": "f4hwn.basic" - } - }, - { - "name": "RescueOps", - "inherits": "default", - "cacheVariables": { - "ENABLE_SPECTRUM": false, - "ENABLE_FMRADIO": false, - "ENABLE_VOX": true, - "ENABLE_AIRCOPY": true, - "ENABLE_FEAT_F4HWN_K5VIEWER": true, - "ENABLE_FEAT_F4HWN_GAME": false, - "ENABLE_FEAT_F4HWN_PMR": true, - "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_NOAA": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, - "EDITION_STRING": "RescueOps", - "TARGET": "f4hwn.rescueops" - } - }, - { - "name": "Game", - "inherits": "default", - "cacheVariables": { - "ENABLE_SPECTRUM": false, - "ENABLE_FMRADIO": true, - "ENABLE_VOX": false, - "ENABLE_AIRCOPY": true, - "ENABLE_FEAT_F4HWN_K5VIEWER": true, - "ENABLE_FEAT_F4HWN_GAME": true, - "ENABLE_FEAT_F4HWN_PMR": true, - "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": false, - "EDITION_STRING": "Game", - "TARGET": "f4hwn.game" - } - }, { "name": "Fusion", "inherits": "default", @@ -199,12 +106,12 @@ "ENABLE_SPECTRUM": true, "ENABLE_FMRADIO": true, "ENABLE_VOX": true, - "ENABLE_AIRCOPY": true, + "ENABLE_AIRCOPY": false, "ENABLE_FEAT_F4HWN_K5VIEWER": true, - "ENABLE_FEAT_F4HWN_GAME": true, + "ENABLE_FEAT_F4HWN_GAME": false, "ENABLE_FEAT_F4HWN_PMR": true, "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true, - "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, + "ENABLE_FEAT_F4HWN_RESCUE_OPS": false, "ENABLE_FEAT_F4HWN_VOL": true, "ENABLE_FEAT_F4HWN_AUDIO": true, "ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true, @@ -212,9 +119,8 @@ "ENABLE_FEAT_F4HWN_SCAN_FASTER": true, "ENABLE_FEAT_F4HWN_SCAN_RSSI": true, "ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true, - "ENABLE_FEAT_F4HWN_BEAM": true, - "ENABLE_FEAT_F4HWN_FOXHUNT": true, - "ENABLE_FEAT_F4HWN_ACTION_PICKER": true, + "ENABLE_FEAT_F4HWN_BEAM": false, + "ENABLE_FEAT_F4HWN_FOXHUNT": false, "ENABLE_FEAT_F4HWN_RXTX_LOG": true, "ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false, "ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true, @@ -223,10 +129,45 @@ "ENABLE_FEAT_F4HWN_LOGO": true, "ENABLE_FEAT_F4HWN_LOGO_SAV": true, "ENABLE_FEAT_F4HWN_MENU_CAT": true, + "ENABLE_FEAT_F4HWN_ACTION_PICKER": true, + "ENABLE_FEAT_F4HWN_MULTIBOOT": true, "ENABLE_SWD": true, "EDITION_STRING": "Fusion", "TARGET": "f4hwn.fusion" } + }, + { + "name": "Transfer", + "inherits": "Fusion", + "cacheVariables": { + "ENABLE_AIRCOPY": true, + "ENABLE_FEAT_F4HWN_BEAM": true, + "EDITION_STRING": "Transfer", + "TARGET": "f4hwn.transfer" + } + }, + { + "name": "Field", + "inherits": "Fusion", + "cacheVariables": { + "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, + "ENABLE_FEAT_F4HWN_FOXHUNT": true, + "EDITION_STRING": "Field", + "TARGET": "f4hwn.field" + } + }, + { + "name": "Extended", + "inherits": "Fusion", + "cacheVariables": { + "ENABLE_AIRCOPY": true, + "ENABLE_FEAT_F4HWN_GAME": true, + "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, + "ENABLE_FEAT_F4HWN_BEAM": true, + "ENABLE_FEAT_F4HWN_FOXHUNT": true, + "EDITION_STRING": "Extended", + "TARGET": "f4hwn.extended" + } } ], "buildPresets": [ @@ -234,29 +175,21 @@ "name": "Custom", "configurePreset": "Custom" }, - { - "name": "Bandscope", - "configurePreset": "Bandscope" - }, - { - "name": "Broadcast", - "configurePreset": "Broadcast" - }, - { - "name": "Basic", - "configurePreset": "Basic" - }, - { - "name": "RescueOps", - "configurePreset": "RescueOps" - }, - { - "name": "Game", - "configurePreset": "Game" - }, { "name": "Fusion", "configurePreset": "Fusion" + }, + { + "name": "Transfer", + "configurePreset": "Transfer" + }, + { + "name": "Field", + "configurePreset": "Field" + }, + { + "name": "Extended", + "configurePreset": "Extended" } ] } diff --git a/README.md b/README.md index c7b353b1..9ec495af 100644 --- a/README.md +++ b/README.md @@ -59,18 +59,23 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol ### Fusion edition -Fusion is the reference edition of the project. It provides an all-in-one firmware for the UV-K1 and UV-K5 V3, including: +Fusion is the generic reference edition for the UV-K1 and UV-K5 V3. It is intended +for everyday use and is the base inherited by the specialized editions. It includes: - Fagci's spectrum analyzer, -- broadcast FM radio, -- VOX and AirCopy, -- BEAM wireless channel transfer, +- broadcast FM radio and VOX, - [UV Studio](https://armel.github.io/uvstudio/) with integrated K5Viewer screen mirroring, screenshots and remote keyboard control, - advanced RX audio profiles and Audio Scope, -- first-responder-oriented controls, - the Breakout game, - automatic RX/TX activity logging with RF Log, -- full Fox Hunt and Morse Beacon support. +- multiboot support. + +Specialized presets extend Fusion for specific uses: + +- **Transfer** adds AirCopy and BEAM wireless channel transfer. +- **Field** adds first-responder controls, Fox Hunt and Morse Beacon support. +- **Extended** combines the Transfer and Field feature sets without claiming to include every optional feature. +- **Custom** remains a manually configured build based directly on the hidden technical default. ### Radio and signal handling @@ -324,9 +329,10 @@ But, they are nice toys for the price, fun to play with. ## Compiling and Building from Docker -This project provides a Docker-based build system to compile the Fusion firmware for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script. - -The documented build output is generated inside `build/Fusion`, using the CMake presets defined in `CMakePresets.json`. +This project provides a Docker-based build system for the UV-K1 and UV-K5 V3. +Everything is handled through the `compile-with-docker.sh` helper script. Fusion is +the default generic preset, while specialized builds are generated in their own +`build/` directories. ### Prerequisites @@ -335,29 +341,37 @@ The documented build output is generated inside `build/Fusion`, using the CMake ### Build Script Overview -The script `compile-with-docker.sh` performs the following actions: +The script `compile-with-docker.sh`: 1. Builds the Docker image (`uvk1-uvk5v3`) if it does not already exist. -2. Removes any previous `build` directory to ensure a clean configuration. -3. Runs CMake using the `Fusion` preset inside the Docker container. -4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files. +2. Configures the selected preset with `cmake --fresh`. +3. Builds the firmware and outputs matching `.elf`, `.bin` and `.hex` files. +4. Displays Flash and RAM usage; `All` keeps the individual build logs quiet. ### Usage ```bash -./compile-with-docker.sh Fusion [extra CMake options] +./compile-with-docker.sh [Preset] [extra CMake options] ``` -### Documented Preset +The default preset is **Fusion**. Available presets are: +- **Custom** - **Fusion** +- **Transfer** +- **Field** +- **Extended** +- **All** (Fusion, Transfer, Field and Extended) -### Examples - -Build Fusion: +Examples: ```bash +./compile-with-docker.sh ./compile-with-docker.sh Fusion +./compile-with-docker.sh Transfer +./compile-with-docker.sh Field +./compile-with-docker.sh Extended +./compile-with-docker.sh All ``` ### Passing Additional CMake Options diff --git a/compile-with-docker.sh b/compile-with-docker.sh index 369acd1a..457e6c21 100755 --- a/compile-with-docker.sh +++ b/compile-with-docker.sh @@ -5,16 +5,21 @@ set -euo pipefail # Usage: # ./compile-with-docker.sh [Preset] [CMake options...] # Examples: -# ./compile-with-docker.sh Custom -# ./compile-with-docker.sh Bandscope -DENABLE_SPECTRUM=ON -# ./compile-with-docker.sh Broadcast -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON +# ./compile-with-docker.sh Fusion +# ./compile-with-docker.sh Transfer +# ./compile-with-docker.sh Field -DENABLE_NOAA=ON +# ./compile-with-docker.sh Custom -DENABLE_SPECTRUM=ON # ./compile-with-docker.sh Fusion -DDEV=ON # ./compile-with-docker.sh All -# Default preset: "Custom" +# Default preset: "Fusion" # --------------------------------------------- IMAGE=uvk1-uvk5v3 -PRESET=${1:-Custom} +RELEASE_PRESETS=(Fusion Transfer Field Extended) +FLASH_LIMIT=$((118 * 1024)) +RAM_LIMIT=$((16 * 1024)) + +PRESET=${1:-Fusion} shift || true # remove preset from arguments if present # Any remaining args will be treated as CMake cache variables @@ -23,53 +28,198 @@ EXTRA_ARGS=("$@") # --------------------------------------------- # Validate preset name # --------------------------------------------- -if [[ ! "$PRESET" =~ ^(Custom|Bandscope|Broadcast|Basic|RescueOps|Game|Fusion|All)$ ]]; then +if [[ ! "$PRESET" =~ ^(Custom|Fusion|Transfer|Field|Extended|All)$ ]]; then echo "❌ Unknown preset: '$PRESET'" - echo "Valid presets are: Custom, Bandscope, Broadcast, Basic, RescueOps, Game, Fusion, All" + echo "Valid presets are: Custom, Fusion, Transfer, Field, Extended, All" exit 1 fi +QUIET=0 +if [[ "$PRESET" == "All" ]]; then + QUIET=1 +fi + # --------------------------------------------- # Build the Docker image (only needed once) # --------------------------------------------- -if [[ "$(docker images -q $IMAGE)" == "" ]]; then +if [[ "$(docker images -q "$IMAGE")" == "" ]]; then echo "Building Docker image..." - docker build -t "$IMAGE" . + docker build -q -t "$IMAGE" . >/dev/null fi - -# --------------------------------------------- -# Clean existing CMake cache to ensure toolchain reload -# --------------------------------------------- -rm -rf build export MSYS_NO_PATHCONV=1 -# --------------------------------------------- -# Function to build one preset -# --------------------------------------------- + +RESULT_PRESETS=() +RESULT_FLASH_SIZES=() +RESULT_RAM_SIZES=() + +run_preset_build() { + local preset="$1" + + docker run --rm \ + -u "$(id -u):$(id -g)" \ + -v "$PWD":/src -w /src "$IMAGE" \ + bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version && + cmake --fresh --preset "$1" "${@:2}" && + cmake --build --preset "$1" -j' \ + bash "$preset" "${EXTRA_ARGS[@]}" +} + build_preset() { local preset="$1" + local preset_slug log_file bin_file flash_size ram_size status + + preset_slug="${preset,,}" + log_file="$(mktemp)" + bin_file="" + + find "build/${preset}" -maxdepth 1 -type f -name 'f4hwn.*' -delete 2>/dev/null || true + + if (( QUIET )); then + printf "Building %-10s ... " "$preset" + if run_preset_build "$preset" >"$log_file" 2>&1; then + status=0 + else + status=$? + fi + else + echo "" + echo "=== 🚀 Building preset: ${preset} ===" + echo "---------------------------------------------" + if run_preset_build "$preset" 2>&1 | tee "$log_file" | awk ' + /^Memory region[[:space:]]+Used Size[[:space:]]+Region Size/ { next } + /^[[:space:]]+RAM:[[:space:]]/ { next } + /^[[:space:]]+FLASH:[[:space:]]/ { next } + { print; fflush() } + '; then + status=0 + else + status=$? + fi + fi + + if (( status != 0 )); then + if (( QUIET )); then + printf "FAILED\n\n" + cat "$log_file" + else + echo "Failed: ${preset}" + fi + rm -f -- "$log_file" + return "$status" + fi + + bin_file="build/${preset}/f4hwn.${preset_slug}.bin" + if [[ -z "$bin_file" || ! -f "$bin_file" ]]; then + if (( QUIET )); then + printf "FAILED\n" + fi + echo "Expected binary not found: $bin_file" + if (( QUIET )); then + cat "$log_file" + fi + rm -f -- "$log_file" + return 1 + fi + + flash_size="$(wc -c < "$bin_file")" + ram_size="$(awk '$1 == "RAM:" { + value = $2 + if ($3 == "KB") value *= 1024 + else if ($3 == "MB") value *= 1024 * 1024 + printf "%.0f\n", value + }' "$log_file" | tail -n 1)" + + if [[ ! "$ram_size" =~ ^[0-9]+$ ]]; then + if (( QUIET )); then + printf "FAILED\n" + fi + echo "Could not read RAM usage from linker output" + if (( QUIET )); then + cat "$log_file" + fi + rm -f -- "$log_file" + return 1 + fi + + RESULT_PRESETS+=("$preset") + RESULT_FLASH_SIZES+=("$flash_size") + RESULT_RAM_SIZES+=("$ram_size") + + if (( QUIET )); then + printf "OK\n" + else + echo "✅ Done: ${preset}" + fi + rm -f -- "$log_file" +} + +print_summary() { + local i + local flash_used flash_free flash_bp flash_pct + local flash_used_kib100 flash_free_kib100 flash_used_kib flash_free_kib + local ram_used ram_free ram_bp ram_pct + local ram_used_kib100 ram_free_kib100 ram_used_kib ram_free_kib + echo "" - echo "=== 🚀 Building preset: ${preset} ===" - echo "---------------------------------------------" - docker run --rm \ - -u $(id -u):$(id -g) \ - -it -v "$PWD":/src -w /src "$IMAGE" \ - bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version && - cmake --preset "$1" "${@:2}" && - cmake --build --preset "$1" -j' \ - bash "${preset}" ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} - echo "✅ Done: ${preset}" + printf "Flash (limit: %d B / %d.00 KiB)\n" "$FLASH_LIMIT" "$((FLASH_LIMIT / 1024))" + printf "%-10s | %10s | %10s | %10s | %10s | %7s\n" \ + "Preset" "Used (B)" "Used (KiB)" "Free (B)" "Free (KiB)" "Usage" + printf "%-10s-+-%10s-+-%10s-+-%10s-+-%10s-+-%7s\n" \ + "----------" "----------" "----------" "----------" "----------" "-------" + + for i in "${!RESULT_PRESETS[@]}"; do + flash_used="${RESULT_FLASH_SIZES[$i]}" + flash_free=$((FLASH_LIMIT - flash_used)) + flash_bp=$(((flash_used * 10000 + FLASH_LIMIT / 2) / FLASH_LIMIT)) + flash_used_kib100=$(((flash_used * 100 + 512) / 1024)) + flash_free_kib100=$(((flash_free * 100 + 512) / 1024)) + printf -v flash_pct "%d.%02d%%" "$((flash_bp / 100))" "$((flash_bp % 100))" + printf -v flash_used_kib "%d.%02d" "$((flash_used_kib100 / 100))" "$((flash_used_kib100 % 100))" + printf -v flash_free_kib "%d.%02d" "$((flash_free_kib100 / 100))" "$((flash_free_kib100 % 100))" + + printf "%-10s | %10d | %10s | %10d | %10s | %7s\n" \ + "${RESULT_PRESETS[$i]}" "$flash_used" "$flash_used_kib" \ + "$flash_free" "$flash_free_kib" "$flash_pct" + done + + echo "" + printf "RAM (limit: %d B / %d.00 KiB)\n" "$RAM_LIMIT" "$((RAM_LIMIT / 1024))" + printf "%-10s | %10s | %10s | %10s | %10s | %7s\n" \ + "Preset" "Used (B)" "Used (KiB)" "Free (B)" "Free (KiB)" "Usage" + printf "%-10s-+-%10s-+-%10s-+-%10s-+-%10s-+-%7s\n" \ + "----------" "----------" "----------" "----------" "----------" "-------" + + for i in "${!RESULT_PRESETS[@]}"; do + ram_used="${RESULT_RAM_SIZES[$i]}" + ram_free=$((RAM_LIMIT - ram_used)) + ram_bp=$(((ram_used * 10000 + RAM_LIMIT / 2) / RAM_LIMIT)) + ram_used_kib100=$(((ram_used * 100 + 512) / 1024)) + ram_free_kib100=$(((ram_free * 100 + 512) / 1024)) + printf -v ram_pct "%d.%02d%%" "$((ram_bp / 100))" "$((ram_bp % 100))" + printf -v ram_used_kib "%d.%02d" "$((ram_used_kib100 / 100))" "$((ram_used_kib100 % 100))" + printf -v ram_free_kib "%d.%02d" "$((ram_free_kib100 / 100))" "$((ram_free_kib100 % 100))" + + printf "%-10s | %10d | %10s | %10d | %10s | %7s\n" \ + "${RESULT_PRESETS[$i]}" "$ram_used" "$ram_used_kib" \ + "$ram_free" "$ram_free_kib" "$ram_pct" + done + + echo "" + if (( ${#RESULT_PRESETS[@]} == 1 )); then + echo "🎉 Preset ${RESULT_PRESETS[0]} built successfully!" + else + echo "🎉 All presets built successfully!" + fi } # --------------------------------------------- # Handle 'All' preset # --------------------------------------------- if [[ "$PRESET" == "All" ]]; then - PRESETS=(Bandscope Broadcast Basic RescueOps Game Fusion) - for p in "${PRESETS[@]}"; do + for p in "${RELEASE_PRESETS[@]}"; do build_preset "$p" done - echo "" - echo "🎉 All presets built successfully!" else build_preset "$PRESET" fi +print_summary