diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 736da5ebe..faf070bd5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -60,6 +60,7 @@ jobs: matrix: toolchain: - tg5040 + - tg5050 steps: - name: Checkout @@ -81,6 +82,7 @@ jobs: matrix: toolchain: - tg5040 + - tg5050 core: ${{ fromJson(needs.core-matrix.outputs.cores) }} steps: - name: Checkout @@ -100,19 +102,11 @@ jobs: build: needs: - - build-core - prepare + - build-core runs-on: ubuntu-24.04-arm env: - PLATFORM: ${{ matrix.toolchain }} RELEASE_NAME: ${{ needs.prepare.outputs.release-name }} - RELEASE_VERSION: ${{ needs.prepare.outputs.next-tag }} - strategy: - fail-fast: true - matrix: - toolchain: - - tg5040 - steps: - name: Checkout uses: actions/checkout@v4.2.2 @@ -125,34 +119,63 @@ jobs: - name: Setup run: make setup - - name: Download Cores + - name: Download Cores (tg5040) uses: actions/download-artifact@v4.3.0 with: - path: workspace/${{ env.PLATFORM }}/cores/output/ - pattern: core-${{ matrix.toolchain }}-* + path: workspace/tg5040/cores/output/ + pattern: core-tg5040-* + merge-multiple: true + + - name: Build (tg5040) + run: make tg5040 + env: + PLATFORM: tg5040 + + - name: Download Cores (tg5050) + uses: actions/download-artifact@v4.3.0 + with: + path: workspace/tg5050/cores/output/ + pattern: core-tg5050-* merge-multiple: true - - name: Build - run: make ${{ matrix.toolchain }} + - name: Build (tg5050) + run: make tg5050 + env: + PLATFORM: tg5050 - name: Special run: make special - # note: when multiple platforms are built - # the package step will need to be revamped to - # merge the release files from each platform into - # a single release - name: Package run: make package - - name: Upload Artifacts + - name: Upload All uses: actions/upload-artifact@v4.6.2 with: - name: release-${{ matrix.toolchain }}.zip - path: releases/ + name: ${{ env.RELEASE_NAME }}-all.zip + path: releases/${{ env.RELEASE_NAME }}-all.zip + + - name: Upload Base + uses: actions/upload-artifact@v4.6.2 + with: + name: ${{ env.RELEASE_NAME }}-base.zip + path: releases/${{ env.RELEASE_NAME }}-base.zip + + - name: Upload Extras + uses: actions/upload-artifact@v4.6.2 + with: + name: ${{ env.RELEASE_NAME }}-extras.zip + path: releases/${{ env.RELEASE_NAME }}-extras.zip + + - name: Skip Attest for PRs from forks + id: skip + if: github.ref != 'refs/heads/main' + run: | + echo '::warning title=Attest skipped::Attest action requires permissions and is performed only for main branch.' - name: Attest Build Provenance uses: actions/attest-build-provenance@v2.3.0 + if: github.ref == 'refs/heads/main' with: subject-path: | releases/${{ env.RELEASE_NAME }}-all.zip @@ -174,7 +197,7 @@ jobs: uses: actions/download-artifact@v4.3.0 with: path: releases/ - pattern: release-* + pattern: NextUI-* merge-multiple: true - name: Create and Push Tag diff --git a/.vscode/launch.json b/.vscode/launch.json index 5057ff015..ccab821f9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -57,6 +57,18 @@ "preLaunchTask": "make desktop", "envFile": "${workspaceFolder}/.env_desktop", }, + { + "name": "App: Clock", + "request": "launch", + "type": "cppdbg", + "osx": { + "MIMode": "lldb" + }, + "cwd": "${workspaceFolder}/workspace/all/clock/build/desktop", + "program": "${workspaceFolder}/workspace/all/clock/build/desktop/clock.elf", + "preLaunchTask": "make desktop", + "envFile": "${workspaceFolder}/.env_desktop", + }, { "name": "App: Gametimectl", "request": "launch", diff --git a/makefile b/makefile index 2c7031a18..2bb42df61 100644 --- a/makefile +++ b/makefile @@ -38,7 +38,7 @@ RELEASE_NAME ?= $(RELEASE_BASE)-$(RELEASE_DOT) VENDOR_DEST := ./build/VENDOR/Tools PACKAGE_URL_MAPPINGS := \ "https://github.com/UncleJunVIP/nextui-pak-store/releases/latest/download/Pak.Store.pakz nextui.pak_store.pakz" \ - "https://github.com/LanderN/nextui-updater-pak/releases/latest/download/nextui-updater-pak.zip nextui.updater.pakz" + "https://github.com/frysee/nextui-updater-pak/releases/latest/download/nextui.updater.pakz nextui.updater.pakz" # add more URLs as needed ########################################################### @@ -100,6 +100,7 @@ endif cp ./workspace/all/nextui/build/$(PLATFORM)/nextui.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/minarch/build/$(PLATFORM)/minarch.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/nextval/build/$(PLATFORM)/nextval.elf ./build/SYSTEM/$(PLATFORM)/bin/ + cp ./workspace/all/clock/build/$(PLATFORM)/clock.elf ./build/EXTRAS/Tools/$(PLATFORM)/Clock.pak/ cp ./workspace/all/minput/build/$(PLATFORM)/minput.elf ./build/EXTRAS/Tools/$(PLATFORM)/Input.pak/ cp ./workspace/all/settings/build/$(PLATFORM)/settings.elf ./build/EXTRAS/Tools/$(PLATFORM)/Settings.pak/ ifeq ($(PLATFORM), tg5040) diff --git a/skeleton/SYSTEM/tg5040/bin/suspend b/skeleton/SYSTEM/tg5040/bin/suspend old mode 100755 new mode 100644 index d0771fa35..cf8753fd5 --- a/skeleton/SYSTEM/tg5040/bin/suspend +++ b/skeleton/SYSTEM/tg5040/bin/suspend @@ -1,5 +1,6 @@ #!/bin/sh -set -euo pipefail +set -uo pipefail +set +e exec 0<&- #logfile="/mnt/SDCARD/.userdata/tg5040/logs/suspend_$(date +%Y%m%d_%H%M%S).log" @@ -8,6 +9,8 @@ exec 0<&- wifid_running= bluetoothd_running= +sleep_retval= + asound_state_dir=/tmp/asound-suspend before() { @@ -28,6 +31,7 @@ before() { >&2 echo "Stopping wpa_supplicant..." /etc/wifi/wifi_init.sh stop || true fi + } after() { @@ -48,10 +52,45 @@ after() { # alsactl --file "$asound_state_dir/asound.state.pre" restore || true } + before + >&2 echo "Suspending..." -echo mem >/sys/power/state +sleep_max_tries=5 +for i in $(seq 1 $sleep_max_tries); do + >&2 echo "Deep sleep attempt $i of $sleep_max_tries" + + sleep_time=$(date +%s) + + echo mem >/sys/power/state + sleep_retval=$? + + >&2 echo "Deep sleep: kernel returned $sleep_retval" + if [ $sleep_retval -eq 0 ]; then + break + fi + + # Really hard to debug this edge case where the system sleeps successfully + # but still returns nonzero + wake_time=$(date +%s) + + time_asleep=$(($wake_time-$sleep_time)) + + # This device has a nasty habit of returning false negatives + # once in a blue moon; if device was asleep for more than + # x seconds, wake up regardless of return value. + if [ $time_asleep -gt 5 ]; then + >&2 echo "Deep sleep: false negative caught - (sleep for $time_asleep secs)" + sleep_retval=0 + break + fi + + + >&2 echo "Deep sleep failed: retrying in 3 seconds" + sleep 3 +done # Resume services in background to reduce UI latency after & +exit $sleep_retval diff --git a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh index 2ad5f8ad8..3b3bda444 100755 --- a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh @@ -56,21 +56,21 @@ start_bt() { # bluealsa -p a2dp-source --keep-alive=-1 & bluealsa -p a2dp-source & sleep 1 + # Power on adapter + bluetoothctl power on 2>/dev/null + + # Set discoverable and pairable + bluetoothctl discoverable on 2>/dev/null + bluetoothctl pairable on 2>/dev/null + + # Set default agent for automatic pairing (no input/output) + bluetoothctl agent NoInputNoOutput 2>/dev/null + bluetoothctl default-agent 2>/dev/null + + # Set adapter name + bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null } - # Power on adapter - bluetoothctl power on 2>/dev/null - - # Set discoverable and pairable - bluetoothctl discoverable on 2>/dev/null - bluetoothctl pairable on 2>/dev/null - - # Set default agent for automatic pairing (no input/output) - bluetoothctl agent NoInputNoOutput 2>/dev/null - bluetoothctl default-agent 2>/dev/null - - # Set adapter name - bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null } ble_start() { @@ -104,16 +104,15 @@ stop_bt() { # stop bluealsa killall bluealsa 2>/dev/null - # stop bluetoothctl - bluetoothctl power off 2>/dev/null - #bluetoothctl discoverable off 2>/dev/null - bluetoothctl pairable off 2>/dev/null - #bluetoothctl remove $(bluetoothctl devices | awk '{print $2}') 2>/dev/null - killall bluetoothctl 2>/dev/null - # Stop bluetooth service d=`ps | grep bluetoothd | grep -v grep` [ -n "$d" ] && { + # stop bluetoothctl + bluetoothctl power off 2>/dev/null + #bluetoothctl discoverable off 2>/dev/null + bluetoothctl pairable off 2>/dev/null + #bluetoothctl remove $(bluetoothctl devices | awk '{print $2}') 2>/dev/null + killall bluetoothctl 2>/dev/null killall bluetoothd sleep 1 } diff --git a/skeleton/SYSTEM/tg5040/etc/wifi/wifi_init.sh b/skeleton/SYSTEM/tg5040/etc/wifi/wifi_init.sh index 86a16fca7..3e3ef87ff 100755 --- a/skeleton/SYSTEM/tg5040/etc/wifi/wifi_init.sh +++ b/skeleton/SYSTEM/tg5040/etc/wifi/wifi_init.sh @@ -1,7 +1,7 @@ #!/bin/sh WIFI_INTERFACE="wlan0" -WPA_SUPPLICANT_CONF="/etc/wifi/wpa_supplicant/wpa_supplicant.conf" +WPA_SUPPLICANT_CONF="/etc/wifi/wpa_supplicant.conf" start() { # Unblock wifi via rfkill @@ -11,7 +11,7 @@ start() { if [ ! -f "$WPA_SUPPLICANT_CONF" ]; then mkdir -p "$(dirname "$WPA_SUPPLICANT_CONF")" cat > "$WPA_SUPPLICANT_CONF" << 'EOF' -# cat /etc/wifi/wpa_supplicant/wpa_supplicant.conf +# cat /etc/wifi/wpa_supplicant.conf ctrl_interface=/etc/wifi/sockets disable_scan_offload=1 update_config=1 diff --git a/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh b/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh index 05ad48e07..09528cf3a 100755 --- a/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh +++ b/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh @@ -66,6 +66,9 @@ if [ -f "/etc/init.d/lcservice" ]; then rm /etc/init.d/lcservice fi +# clear shadercache unconditionally, until it properly invalidates itself +rm -rf $SDCARD_PATH/.shadercache + #PD11 pull high for VCC-5v echo 107 > /sys/class/gpio/export echo -n out > /sys/class/gpio/gpio107/direction diff --git a/skeleton/SYSTEM/tg5050/bin/suspend b/skeleton/SYSTEM/tg5050/bin/suspend index 27e0283f5..4a5a9d4b3 100644 --- a/skeleton/SYSTEM/tg5050/bin/suspend +++ b/skeleton/SYSTEM/tg5050/bin/suspend @@ -1,5 +1,6 @@ #!/bin/sh -set -euo pipefail +set -uo pipefail +set -e exec 0<&- #logfile="/mnt/SDCARD/.userdata/tg5040/logs/suspend_$(date +%Y%m%d_%H%M%S).log" @@ -8,6 +9,8 @@ exec 0<&- wifid_running= bluetoothd_running= +sleep_retval= + asound_state_dir=/tmp/asound-suspend before() { @@ -51,7 +54,24 @@ after() { before >&2 echo "Suspending..." -echo mem >/sys/power/state +sleep_max_tries=10 +for i in $(seq 1 $sleep_max_tries); do + >&2 echo "Deep sleep attempt $i of $sleep_max_tries" + + # Can't use | true here, it would crash out on non-zero return. + echo mem >/sys/power/state + sleep_retval=$? + + >&2 echo "Deep sleep: kernel returned $sleep_retval" + if [ $sleep_retval -eq 0 ]; then + break + fi + + >&2 echo "Deep sleep failed: retrying in 2 seconds" + sleep 2 +done # Resume services in background to reduce UI latency after & + +exit $sleep_retval diff --git a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh index fedd04bf2..e7a49309c 100755 --- a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh @@ -64,37 +64,36 @@ start_bt() { # bluealsa -p a2dp-source --keep-alive=-1 & bluealsa -p a2dp-source & sleep 1 + # Power on adapter + bluetoothctl power on 2>/dev/null + + # Set discoverable and pairable + bluetoothctl discoverable on 2>/dev/null + bluetoothctl pairable on 2>/dev/null + + # Set default agent for automatic pairing (no input/output) + bluetoothctl agent NoInputNoOutput 2>/dev/null + bluetoothctl default-agent 2>/dev/null + + # Set adapter name + bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null } - # Power on adapter - bluetoothctl power on 2>/dev/null - - # Set discoverable and pairable - bluetoothctl discoverable on 2>/dev/null - bluetoothctl pairable on 2>/dev/null - - # Set default agent for automatic pairing (no input/output) - bluetoothctl agent NoInputNoOutput 2>/dev/null - bluetoothctl default-agent 2>/dev/null - - # Set adapter name - bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null } stop_bt() { # stop bluealsa killall bluealsa 2>/dev/null - # stop bluetoothctl - bluetoothctl power off 2>/dev/null - #bluetoothctl discoverable off 2>/dev/null - bluetoothctl pairable off 2>/dev/null - #bluetoothctl remove $(bluetoothctl devices | awk '{print $2}') 2>/dev/null - killall bluetoothctl 2>/dev/null - # Stop bluetooth service d=`ps | grep bluetoothd | grep -v grep` [ -n "$d" ] && { + # stop bluetoothctl + bluetoothctl power off 2>/dev/null + #bluetoothctl discoverable off 2>/dev/null + bluetoothctl pairable off 2>/dev/null + #bluetoothctl remove $(bluetoothctl devices | awk '{print $2}') 2>/dev/null + killall bluetoothctl 2>/dev/null killall bluetoothd sleep 1 } diff --git a/skeleton/SYSTEM/tg5050/etc/wifi/wifi_init.sh b/skeleton/SYSTEM/tg5050/etc/wifi/wifi_init.sh index 5f684c521..5ec7f9657 100755 --- a/skeleton/SYSTEM/tg5050/etc/wifi/wifi_init.sh +++ b/skeleton/SYSTEM/tg5050/etc/wifi/wifi_init.sh @@ -15,6 +15,8 @@ start() { # Bring up the interface ip link set $WIFI_INTERFACE up 2>/dev/null + + mkdir -p /etc/wifi/sockets # Create default wpa_supplicant.conf if it doesn't exist if [ ! -f "$WPA_SUPPLICANT_CONF" ]; then @@ -31,7 +33,7 @@ EOF # Start wpa_supplicant if not running if ! pidof wpa_supplicant > /dev/null 2>&1; then - wpa_supplicant -B -i $WIFI_INTERFACE -c $WPA_SUPPLICANT_CONF -D nl80211 2>/dev/null + wpa_supplicant -B -i $WIFI_INTERFACE -c $WPA_SUPPLICANT_CONF -O /etc/wifi/sockets -D nl80211 2>/dev/null sleep 0.5 fi @@ -43,7 +45,7 @@ EOF stop() { # Disconnect and disable - wpa_cli -i $WIFI_INTERFACE disconnect 2>/dev/null + wpa_cli -p /etc/wifi/sockets -i $WIFI_INTERFACE disconnect 2>/dev/null # Bring down interface ip link set $WIFI_INTERFACE down 2>/dev/null diff --git a/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh b/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh index 252feb958..d73b92a2a 100755 --- a/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh +++ b/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh @@ -141,13 +141,6 @@ batmon.elf & # &> $SDCARD_PATH/batmon.txt & rm -f $USERDATA_PATH/.asoundrc audiomon.elf & #&> $SDCARD_PATH/audiomon.txt & -# BT handling -bluetoothon=$(nextval.elf bluetooth | sed -n 's/.*"bluetooth": \([0-9]*\).*/\1/p') -if [ "$bluetoothon" -eq 1 ]; then - $SYSTEM_PATH/etc/bluetooth/bt_init.sh start > /dev/null 2>&1 & -fi -echo after bluetooth `cat /proc/uptime` >> /tmp/nextui_boottime - # wifi handling wifion=$(nextval.elf wifi | sed -n 's/.*"wifi": \([0-9]*\).*/\1/p') if [ "$wifion" -eq 1 ]; then @@ -155,6 +148,13 @@ if [ "$wifion" -eq 1 ]; then fi echo after wifi `cat /proc/uptime` >> /tmp/nextui_boottime +# BT handling +bluetoothon=$(nextval.elf bluetooth | sed -n 's/.*"bluetooth": \([0-9]*\).*/\1/p') +if [ "$bluetoothon" -eq 1 ]; then + $SYSTEM_PATH/etc/bluetooth/bt_init.sh start > /dev/null 2>&1 & +fi +echo after bluetooth `cat /proc/uptime` >> /tmp/nextui_boottime + ####################################### AUTO_PATH=$USERDATA_PATH/auto.sh diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.c index 7202ae10b..38970b455 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.c @@ -28,7 +28,7 @@ int main(int argc , char* argv[]) { InitSettings(); // TODO: make use of SCALE1() - SDL_Surface* digits = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(120,16), FIXED_DEPTH,RGBA_MASK_AUTO); + SDL_Surface* digits = SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE, SCALE1(120), SCALE1(16), 32, screen->format->format); SDL_FillRect(digits, NULL, RGB_BLACK); SDL_Surface* digit; diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 3a9343148..daa4dd6d3 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -3267,27 +3267,51 @@ FALLBACK_IMPLEMENTATION int PLAT_supportsDeepSleep(void) { return 0; } FALLBACK_IMPLEMENTATION int PLAT_deepSleep(void) { const char *state_path = "/sys/power/state"; + int state_fd = 0; - int state_fd = open(state_path, O_WRONLY); - if (state_fd < 0) - { - LOG_error("failed to open %s: %d\n", state_path, errno); - return -1; - } + for (int i = 0; i < 5; i++) { - LOG_info("suspending to RAM\n"); - int ret = write(state_fd, "mem", 3); - if (ret < 0) - { - // Can fail shortly after resuming with EBUSY - LOG_error("failed to set power state: %d\n", errno); + // Check for power button press while waiting to retry + uint32_t attempt_ticks = SDL_GetTicks(); + if (i > 0) { // Don't wait on first attempt + while (1) { + if (pwr.requested_wake || PAD_wake()) { + pwr.requested_wake = 0; + return 0; + } + SDL_Delay(200); + if (SDL_GetTicks() - attempt_ticks >= 2000) { + break; + } + } + } + + state_fd = open(state_path, O_WRONLY); + if (state_fd < 0) + { + LOG_error("failed to open %s: %d\n", state_path, errno); + LOG_info("retrying suspend in 2 seconds...\n"); + close(state_fd); + continue; + } + + LOG_info("suspending to RAM\n"); + int ret = write(state_fd, "mem", 3); + if (ret < 0) + { + // Can fail shortly after resuming with EBUSY + LOG_error("failed to set power state: %d\n", errno); + LOG_info("retrying suspend in 2 seconds...\n"); + close(state_fd); + continue; + } + + LOG_info("returned from suspend\n"); close(state_fd); - return -1; + return 0; } - LOG_info("returned from suspend\n"); - close(state_fd); - return 0; + return -1; } int PAD_anyJustPressed(void) { return pad.just_pressed != BTN_NONE; } @@ -3757,11 +3781,11 @@ static void PWR_exitSleep(void) VIB_singlePulse(VIB_sleepStrength, VIB_sleepDuration_ms); } PLAT_enableBacklight(1); + SND_overrideMute(1); SetVolume(GetVolume()); } // reinitialize audio after sleep otherwise it doesnt come back on sometimes LOG_info("Reinitialize audio after sleep\n"); - SND_overrideMute(1); SND_resetAudio(snd.sample_rate_in, snd.frame_rate); sync(); @@ -3796,13 +3820,6 @@ static void PWR_waitForWake(void) { return; } - else if (deep_sleep_attempts < 3) - { - LOG_warn("failed to enter deep sleep - retrying in 5 seconds\n"); - sleep_ticks += 5000; - deep_sleep_attempts++; - continue; - } else { LOG_warn("failed to enter deep sleep - powering off\n"); diff --git a/workspace/all/common/generic_bt.c b/workspace/all/common/generic_bt.c index 7dbc07a1a..1f017f4e2 100644 --- a/workspace/all/common/generic_bt.c +++ b/workspace/all/common/generic_bt.c @@ -20,6 +20,7 @@ #include "utils.h" #include +#include bool PLAT_hasBluetooth() { return true; } bool PLAT_bluetoothEnabled() { return CFG_getBluetooth(); } @@ -27,6 +28,48 @@ bool PLAT_bluetoothEnabled() { return CFG_getBluetooth(); } #define btlog(fmt, ...) \ LOG_note(PLAT_bluetoothDiagnosticsEnabled() ? LOG_INFO : LOG_DEBUG, fmt, ##__VA_ARGS__) +// Forward declaration +static int bt_run_cmd(const char *cmd, char *output, size_t output_len); + +// Bluetoothctl version detection +static int bluetoothctl_major_version = 0; +static int bluetoothctl_minor_version = 0; + +static void bt_detect_version(void) { + static bool detected = false; + if (detected) return; + + char output[256]; + if (bt_run_cmd("bluetoothctl --version 2>/dev/null | head -1", output, sizeof(output)) == 0) { + // Parse version like "bluetoothctl: 5.54" or "5.78" + int major = 0, minor = 0; + if (sscanf(output, "bluetoothctl: %d.%d", &major, &minor) == 2 || + sscanf(output, "%d.%d", &major, &minor) == 2) { + bluetoothctl_major_version = major; + bluetoothctl_minor_version = minor; + btlog("Detected bluetoothctl version %d.%d\n", major, minor); + } else { + // Default to 5.54 if detection fails + bluetoothctl_major_version = 5; + bluetoothctl_minor_version = 54; + btlog("Failed to detect bluetoothctl version, assuming 5.54\n"); + } + } else { + // Assume older version if --version doesn't work + bluetoothctl_major_version = 5; + bluetoothctl_minor_version = 54; + btlog("bluetoothctl --version failed, assuming 5.54\n"); + } + detected = true; +} + +// Helper to check if bluetoothctl version is >= specified version +static bool bt_version_gte(int major, int minor) { + if (bluetoothctl_major_version > major) return true; + if (bluetoothctl_major_version == major && bluetoothctl_minor_version >= minor) return true; + return false; +} + // Device class definitions for parsing #define COD_MAJOR_MASK 0x1F00 #define GET_MAJOR_CLASS(cod) ((cod & COD_MAJOR_MASK) >> 8) @@ -214,6 +257,9 @@ void PLAT_bluetoothInit() { return; } + // Detect bluetoothctl version + bt_detect_version(); + bt_initialized = true; PLAT_bluetoothEnable(CFG_getBluetooth()); } @@ -254,12 +300,22 @@ void PLAT_bluetoothDiscovery(int on) { btlog("Starting BT discovery.\n"); // Clear old discovered devices bt_clear_discovered_devices(); - // Start scanning - system("bluetoothctl --timeout 60 scan on 2>/dev/null &"); + + // Start scanning - version-dependent command + if (bt_version_gte(5, 70)) { + // In 5.70+, timeout option works differently + // Start scan in background and schedule auto-stop + system("sh -c 'bluetoothctl scan on 2>/dev/null & BT_PID=$!; sleep 60; bluetoothctl scan off 2>/dev/null; kill $BT_PID 2>/dev/null' &"); + } else { + // For 5.54 and similar versions + system("bluetoothctl --timeout 60 scan on 2>/dev/null &"); + } bt_discovering = true; } else { btlog("Stopping BT discovery.\n"); system("bluetoothctl scan off 2>/dev/null"); + // Also try to kill any background scan processes + system("pkill -f 'bluetoothctl scan on' 2>/dev/null"); bt_discovering = false; } } @@ -342,9 +398,16 @@ int PLAT_bluetoothPaired(struct BT_devicePaired *paired, int max) { return 0; } - // Get list of paired devices + // Get list of paired devices - try both command formats char output[8192]; - if (bt_run_cmd("bluetoothctl paired-devices 2>/dev/null", output, sizeof(output)) != 0) { + int ret = bt_run_cmd("bluetoothctl paired-devices 2>/dev/null", output, sizeof(output)); + + // If paired-devices doesn't work (5.78+), try alternative command + if (ret != 0 || strlen(output) == 0) { + ret = bt_run_cmd("bluetoothctl devices Paired 2>/dev/null", output, sizeof(output)); + } + + if (ret != 0) { btlog("Failed to get paired device list\n"); return 0; } @@ -398,11 +461,22 @@ void PLAT_bluetoothPair(char *addr) { snprintf(cmd, sizeof(cmd), "bluetoothctl trust %s 2>/dev/null", addr); system(cmd); + // Small delay to ensure trust command completes + usleep(100000); + // Pair with the device snprintf(cmd, sizeof(cmd), "bluetoothctl pair %s 2>/dev/null", addr); int ret = system(cmd); if (ret != 0) { LOG_error("BT pair failed: %d\n", ret); + // In newer versions, try alternative pairing method + if (bt_version_gte(5, 70)) { + snprintf(cmd, sizeof(cmd), "echo 'pair %s' | bluetoothctl 2>/dev/null", addr); + ret = system(cmd); + if (ret != 0) { + LOG_error("BT pair (alternative method) failed: %d\n", ret); + } + } } // Remove from discovered list since it's now paired diff --git a/workspace/all/common/generic_video.c b/workspace/all/common/generic_video.c index 5dabcc69e..a0b9f3624 100644 --- a/workspace/all/common/generic_video.c +++ b/workspace/all/common/generic_video.c @@ -2114,9 +2114,7 @@ void PLAT_GL_Swap() { //} } - - -// tryin to some arm neon optimization for first time for flipping image upside down, they sit in platform cause not all have neon extensions +// flipping image upside down void PLAT_pixelFlipper(uint8_t* pixels, int width, int height) { const int rowBytes = width * 4; uint8_t* rowTop; @@ -2127,6 +2125,8 @@ void PLAT_pixelFlipper(uint8_t* pixels, int width, int height) { rowBottom = pixels + (height - 1 - y) * rowBytes; int x = 0; +// NEON optimization for compatible ARM architectures +#if defined(__ARM_NEON) || defined(__ARM_NEON__) for (; x + 15 < rowBytes; x += 16) { uint8x16_t top = vld1q_u8(rowTop + x); uint8x16_t bottom = vld1q_u8(rowBottom + x); @@ -2134,6 +2134,7 @@ void PLAT_pixelFlipper(uint8_t* pixels, int width, int height) { vst1q_u8(rowTop + x, bottom); vst1q_u8(rowBottom + x, top); } +#endif for (; x < rowBytes; ++x) { uint8_t temp = rowTop[x]; rowTop[x] = rowBottom[x]; diff --git a/workspace/all/common/generic_wifi.c b/workspace/all/common/generic_wifi.c index 29cf263a2..af59ca3ee 100644 --- a/workspace/all/common/generic_wifi.c +++ b/workspace/all/common/generic_wifi.c @@ -77,6 +77,34 @@ static bool wifi_get_ip(char *ip, size_t len) { return false; } +// Helper to escape a string for wpa_cli (double quotes/backslashes) and shell (single quotes) +static void wifi_escape(char *dest, const char *src, size_t dest_len) { + size_t j = 0; + for (size_t i = 0; src[i] != '\0' && j < dest_len - 1; i++) { + // Double quotes and backslashes need to be escaped for wpa_cli (inside its own quotes) + if (src[i] == '"' || src[i] == '\\') { + if (j < dest_len - 2) { + dest[j++] = '\\'; + } + } + // Single quotes need to be escaped for the shell (outside wpa_cli's quotes but inside shell's) + else if (src[i] == '\'') { + if (j < dest_len - 5) { + dest[j++] = '\''; // close single quote + dest[j++] = '\\'; // escape + dest[j++] = '\''; // the actual quote + dest[j++] = '\''; // open single quote again + } + continue; + } + + if (j < dest_len - 1) { + dest[j++] = src[i]; + } + } + dest[j] = '\0'; +} + void PLAT_wifiInit() { // We should never have to do this manually, as wifi_init.sh should be // started/stopped by the platform init scripts. @@ -467,11 +495,32 @@ void PLAT_wifiConnectPass(const char *ssid, WifiSecurityType sec, const char* pa return; } + // Validation + for (int i = 0; ssid[i]; i++) { + if (ssid[i] == '\t' || ssid[i] == '\n' || ssid[i] == '\r') { + LOG_error("PLAT_wifiConnectPass: SSID contains invalid characters\n"); + return; + } + } + if (pass) { + for (int i = 0; pass[i]; i++) { + if (pass[i] == '\n' || pass[i] == '\r') { + LOG_error("PLAT_wifiConnectPass: Password contains invalid characters\n"); + return; + } + } + } + wifilog("PLAT_wifiConnectPass: Attempting to connect to SSID '%s' (security=%d)\n", ssid, sec); + char escaped_ssid[SSID_MAX * 5]; + char escaped_pass[SSID_MAX * 5]; + wifi_escape(escaped_ssid, ssid, sizeof(escaped_ssid)); + if (pass) wifi_escape(escaped_pass, pass, sizeof(escaped_pass)); + // Check if network already exists int network_id = wifi_find_network_id(ssid); - char cmd[512]; + char cmd[1024]; char output[128]; if (network_id < 0) { @@ -486,13 +535,13 @@ void PLAT_wifiConnectPass(const char *ssid, WifiSecurityType sec, const char* pa // Set SSID (needs quotes for wpa_cli) wifilog("Setting network SSID...\n"); - snprintf(cmd, sizeof(cmd), "%s set_network %d ssid '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, ssid); + snprintf(cmd, sizeof(cmd), "%s set_network %d ssid '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, escaped_ssid); system(cmd); // Set password or open network if (pass && pass[0] != '\0') { wifilog("Setting network password...\n"); - snprintf(cmd, sizeof(cmd), "%s set_network %d psk '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, pass); + snprintf(cmd, sizeof(cmd), "%s set_network %d psk '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, escaped_pass); system(cmd); } else if (sec == SECURITY_NONE) { wifilog("Configuring as open network...\n"); @@ -506,7 +555,7 @@ void PLAT_wifiConnectPass(const char *ssid, WifiSecurityType sec, const char* pa } else if (pass && pass[0] != '\0') { // Update password for existing network wifilog("Updating password for existing network...\n"); - snprintf(cmd, sizeof(cmd), "%s set_network %d psk '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, pass); + snprintf(cmd, sizeof(cmd), "%s set_network %d psk '\"%s\"' 2>/dev/null", WPA_CLI_CMD, network_id, escaped_pass); system(cmd); system(WPA_CLI_CMD " save_config 2>/dev/null"); } else { diff --git a/workspace/all/common/utils.c b/workspace/all/common/utils.c index ad19aeec2..c4315885c 100644 --- a/workspace/all/common/utils.c +++ b/workspace/all/common/utils.c @@ -1,4 +1,6 @@ +#ifndef _GNU_SOURCE #define _GNU_SOURCE // for strcasestr +#endif #include #include #include @@ -514,4 +516,4 @@ int clamp(int x, int lower, int upper) double clampd(double x, double lower, double upper) { return min(upper, max(x, lower)); -} \ No newline at end of file +} diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 4fd5750c2..7172d46c6 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -956,6 +956,7 @@ static void State_getPath(char* filename) { } } +#define RASTATE_HEADER_SIZE 16 static void State_read(void) { // from picoarch size_t state_size = core.serialize_size(); if (!state_size) return; @@ -972,58 +973,44 @@ static void State_read(void) { // from picoarch char filename[MAX_PATH]; State_getPath(filename); + uint8_t rastate_header[RASTATE_HEADER_SIZE] = {0}; + #ifdef HAS_SRM - RFILE *state_rfile = NULL; rzipstream_t *state_rzfile = NULL; - // TODO: rzipstream_open can also handle uncompressed, else branch is probably unnecessary - // srm, potentially compressed - if (CFG_getStateFormat() == STATE_FORMAT_SRM || CFG_getStateFormat() == STATE_FORMAT_SRM_EXTRADOT) { - state_rzfile = rzipstream_open(filename, RETRO_VFS_FILE_ACCESS_READ); - if(!state_rzfile) { - if (state_slot!=8) { // st8 is a default state in MiniUI and may not exist, that's okay - LOG_error("Error opening state file: %s (%s)\n", filename, strerror(errno)); - } - goto error; - } + state_rzfile = rzipstream_open(filename, RETRO_VFS_FILE_ACCESS_READ); + if(!state_rzfile) { + if (state_slot!=8) { // st8 is a default state in MiniUI and may not exist, that's okay + LOG_error("Error opening state file: %s (%s)\n", filename, strerror(errno)); + } + goto error; + } + if (rzipstream_read(state_rzfile, rastate_header, RASTATE_HEADER_SIZE) < RASTATE_HEADER_SIZE) { + LOG_error("Error reading rastate header from file: %s (%s)\n", filename, strerror(errno)); + goto error; + } - // some cores report the wrong serialize size initially for some games, eg. mgba: Wario Land 4 - // so we allow a size mismatch as long as the actual size fits in the buffer we've allocated - if (state_size < rzipstream_read(state_rzfile, state, state_size)) { - LOG_error("Error reading state data from file: %s (%s)\n", filename, strerror(errno)); - goto error; - } + if (memcmp(rastate_header, "RASTATE", 7) != 0) { + // This file only contains raw core state data + rzipstream_rewind(state_rzfile); + } + // No need to parse the header any further + // (we only need MEM section which will always be the first one) - if (!core.unserialize(state, state_size)) { - LOG_error("Error restoring save state: %s (%s)\n", filename, strerror(errno)); - goto error; - } + // some cores report the wrong serialize size initially for some games, eg. mgba: Wario Land 4 + // so we allow a size mismatch as long as the actual size fits in the buffer we've allocated + if (state_size < rzipstream_read(state_rzfile, state, state_size)) { + LOG_error("Error reading state data from file: %s (%s)\n", filename, strerror(errno)); + goto error; } - else { - state_rfile = filestream_open(filename, RETRO_VFS_FILE_ACCESS_READ, 0); - if (!state_rfile) { - if (state_slot!=8) { // st8 is a default state in MiniUI and may not exist, that's okay - LOG_error("Error opening state file: %s (%s)\n", filename, strerror(errno)); - } - goto error; - } - - // some cores report the wrong serialize size initially for some games, eg. mgba: Wario Land 4 - // so we allow a size mismatch as long as the actual size fits in the buffer we've allocated - if (state_size < filestream_read(state_rfile, state, state_size)) { - LOG_error("Error reading state data from file: %s (%s)\n", filename, strerror(errno)); - goto error; - } - - if (!core.unserialize(state, state_size)) { - LOG_error("Error restoring save state: %s (%s)\n", filename, strerror(errno)); - goto error; - } + + if (!core.unserialize(state, state_size)) { + LOG_error("Error restoring save state: %s\n", filename); + goto error; } error: if (state) free(state); - if (state_rfile) filestream_close(state_rfile); if (state_rzfile) rzipstream_close(state_rzfile); #else FILE *state_file = fopen(filename, "r"); @@ -1033,6 +1020,16 @@ static void State_read(void) { // from picoarch } goto error; } + + if (fread(rastate_header, 1, RASTATE_HEADER_SIZE, state_file) < RASTATE_HEADER_SIZE) { + LOG_error("Error reading rastate header from file: %s (%s)\n", filename, strerror(errno)); + goto error; + } + + if (memcmp(rastate_header, "RASTATE", 7) != 0) { + // This file only contains raw core state data; rewind + fseek(state_file, 0, SEEK_SET); + } // some cores report the wrong serialize size initially for some games, eg. mgba: Wario Land 4 // so we allow a size mismatch as long as the actual size fits in the buffer we've allocated @@ -1042,7 +1039,7 @@ static void State_read(void) { // from picoarch } if (!core.unserialize(state, state_size)) { - LOG_error("Error restoring save state: %s (%s)\n", filename, strerror(errno)); + LOG_error("Error restoring save state: %s\n", filename); goto error; } @@ -4217,13 +4214,13 @@ void fillRect(int x, int y, int w, int h, uint32_t c, uint32_t *data, int stride } static void blitBitmapText(char* text, int ox, int oy, uint32_t* data, int stride, int width, int height) { - #define CHAR_WIDTH 5 - #define CHAR_HEIGHT 9 + #define DEBUG_CHAR_WIDTH 5 + #define DEBUG_CHAR_HEIGHT 9 #define LETTERSPACING 1 int len = strlen(text); - int w = ((CHAR_WIDTH + LETTERSPACING) * len) - 1; - int h = CHAR_HEIGHT; + int w = ((DEBUG_CHAR_WIDTH + LETTERSPACING) * len) - 1; + int h = DEBUG_CHAR_HEIGHT; if (ox < 0) ox = width - w + ox; if (oy < 0) oy = height - h + oy; @@ -4248,10 +4245,10 @@ static void blitBitmapText(char* text, int ox, int oy, uint32_t* data, int strid for (int i = 0; i < len; i++) { const char* c = bitmap_font[(unsigned char)text[i]]; if (!c) c = bitmap_font[' ']; - for (int x = 0; x < CHAR_WIDTH; x++) { + for (int x = 0; x < DEBUG_CHAR_WIDTH; x++) { if (current_x >= w) break; - if (c[y * CHAR_WIDTH + x] == '1') { + if (c[y * DEBUG_CHAR_WIDTH + x] == '1') { data[y * stride + current_x] = 0xFFFFFFFF; // white ARGBB8888 } current_x++; diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.c index a76b4a1ae..c2aaefd3e 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.c @@ -1451,10 +1451,14 @@ static void openDirectory(char* path, int auto_launch) { Array_push(stack, top); } else { + // keep a copy of path, which might be a reference into stack which is about to be freed + char temp_path[256]; + strcpy(temp_path, path); + // construct a fresh stack by walking upwards until SDCARD_ROOT DirectoryArray_free(stack); - stack = pathToStack(path); + stack = pathToStack(temp_path); top = stack->items[stack->count - 1]; } } @@ -2042,6 +2046,9 @@ int animWorker(void* unused) { } for (int frame = 0; frame <= total_frames; frame++) { + // Check for shutdown at start of each frame + if (SDL_AtomicGet(&workerThreadsShutdown)) break; + float t = (float)frame / total_frames; if (t > 1.0f) t = 1.0f; @@ -2060,7 +2067,7 @@ int animWorker(void* unused) { if(frame >= total_frames) finaltask->done=1; task->callback(finaltask); SDL_LockMutex(frameMutex); - while (!frameReady) { + while (!frameReady && !SDL_AtomicGet(&workerThreadsShutdown)) { SDL_CondWait(flipCond, frameMutex); } frameReady = false; @@ -2152,6 +2159,7 @@ void cleanupImageLoaderPool() { if (bgqueueCond) SDL_CondSignal(bgqueueCond); if (thumbqueueCond) SDL_CondSignal(thumbqueueCond); if (animqueueCond) SDL_CondSignal(animqueueCond); + if (flipCond) SDL_CondSignal(flipCond); // Wake up animWorker if stuck waiting for frame flip // Wait for all worker threads to finish if (bgLoadThread) { diff --git a/workspace/all/settings/btmenu.cpp b/workspace/all/settings/btmenu.cpp index 21d5f771e..470d40daf 100644 --- a/workspace/all/settings/btmenu.cpp +++ b/workspace/all/settings/btmenu.cpp @@ -143,7 +143,7 @@ void Menu::updater() std::vector kl(SCAN_MAX_RESULTS); int known = BT_pairedDevices(kl.data(), SCAN_MAX_RESULTS); for (int i = 0; i < known; i++) - pairedMap.emplace(kl[i].remote_name, kl[i]); + pairedMap.emplace(kl[i].remote_addr, kl[i]); // Use MAC address as key (unique) // grab list and compare it to previous result // only relayout the menu if changes happended diff --git a/workspace/all/settings/settings.cpp b/workspace/all/settings/settings.cpp index 8400739b7..441216610 100644 --- a/workspace/all/settings/settings.cpp +++ b/workspace/all/settings/settings.cpp @@ -7,6 +7,7 @@ extern "C" #include "utils.h" } +#include #include #include #include