diff --git a/Makefile b/Makefile index fc013656..c8f08f5d 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ endif # Default platforms to build (can be overridden with PLATFORMS=...) ifeq (,$(PLATFORMS)) -PLATFORMS = miyoomini trimuismart rg35xx rg35xxplus my355 tg5040 zero28 rgb30 m17 my282 magicmini +PLATFORMS = miyoomini trimuismart rg35xx rg35xxplus my355 tg5040 zero28 rgb30 m17 my282 magicmini rk3566 endif ########################################################### @@ -92,7 +92,7 @@ endif export OPT_FLAGS export LOG_FLAGS -.PHONY: help build test coverage lint format dev dev-run dev-run-4x3 dev-run-16x9 dev-clean all shell name clean setup special tidy stage compress package dev-deploy dev-build-deploy release release-patch release-minor release-major +.PHONY: help build test coverage lint format dev dev-run dev-run-4x3 dev-run-16x9 dev-clean all shell name clean setup lessos special tidy stage compress package dev-deploy dev-build-deploy release release-patch release-minor release-major export MAKEFLAGS=--no-print-directory @@ -313,7 +313,7 @@ setup: name @echo "Generating emulator paks..." @./scripts/generate-paks.sh all -# Platform-specific packaging for Miyoo/Trimui family +# Platform-specific packaging for Miyoo/Trimui/LessOS families special: @mkdir -p ./build/BOOT/common/install @rsync -a ./skeleton/SYSTEM/common/log.sh ./build/BOOT/common/install/ @@ -323,9 +323,11 @@ special: @mv ./build/BOOT/miyoo ./build/BASE/ @mv ./build/BOOT/trimui ./build/BASE/ @mv ./build/BOOT/magicx ./build/BASE/ + @if [ -d ./build/BOOT/lessos ]; then mv ./build/BOOT/lessos ./build/BASE/; fi @rsync -a ./build/BOOT/.tmp_update/ ./build/BASE/miyoo/app/.tmp_update/ @rsync -a ./build/BOOT/.tmp_update/ ./build/BASE/trimui/app/.tmp_update/ @rsync -a ./build/BOOT/.tmp_update/ ./build/BASE/magicx/.tmp_update/ + @if [ -d ./build/BASE/lessos ]; then rsync -a ./build/BOOT/.tmp_update/ ./build/BASE/lessos/.tmp_update/; fi @rsync -a ./build/BASE/miyoo/ ./build/BASE/miyoo354/ @rsync -a ./build/BASE/miyoo/ ./build/BASE/miyoo355/ @rsync -a ./build/BASE/miyoo/ ./build/BASE/miyoo285/ @@ -389,7 +391,7 @@ compress: cd ./build/PAYLOAD && 7z a -t7z -mx=9 -md=16m -mmt=on LessUI.7z .system; \ fi @mv ./build/PAYLOAD/LessUI.7z ./build/BASE - @cd ./build/BASE && 7z a -tzip -mmt=on -mx=5 ../../releases/$(RELEASE_NAME).zip Tools Bios Roms Saves bin miyoo miyoo354 trimui rg35xx rg35xxplus miyoo355 magicx miyoo285 em_ui.sh LessUI.7z README.txt + @cd ./build/BASE && 7z a -tzip -mmt=on -mx=5 ../../releases/$(RELEASE_NAME).zip Tools Bios Roms Saves bin miyoo miyoo354 trimui rg35xx rg35xxplus miyoo355 magicx miyoo285 lessos em_ui.sh LessUI.7z README.txt @echo "$(RELEASE_NAME)" > ./build/latest.txt # Package: full release build (stage + compress) diff --git a/Makefile.qa b/Makefile.qa index 1c9cf14f..f5763f1e 100644 --- a/Makefile.qa +++ b/Makefile.qa @@ -233,6 +233,7 @@ analyze-native: check-scan-build TEST_CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-parameter TEST_INCLUDES = -I tests/support -I tests/vendor/unity -I workspace/all/common -I workspace/all/launcher -I workspace/all/player -I workspace/all/vendor/stb TEST_UNITY = tests/vendor/unity/unity.c +PATHS_STUB = tests/support/paths_stub.c # All test executables (built from tests/unit/ and tests/integration/) TEST_EXECUTABLES = tests/utils_test tests/nointro_parser_test tests/pad_test tests/gfx_text_test tests/audio_resampler_test tests/player_paths_test tests/launcher_utils_test tests/m3u_parser_test tests/launcher_file_utils_test tests/map_parser_test tests/collection_parser_test tests/recent_parser_test tests/recent_writer_test tests/recent_runtime_test tests/directory_utils_test tests/binary_file_utils_test tests/ui_layout_test tests/str_compare_test tests/effect_system_test tests/effect_generate_test tests/player_utils_test tests/player_config_test tests/player_options_test tests/platform_variant_test tests/launcher_entry_test tests/directory_index_test tests/player_archive_test tests/player_memory_test tests/player_state_test tests/launcher_launcher_test tests/player_cpu_test tests/player_input_test tests/launcher_state_test tests/player_menu_test tests/player_env_test tests/player_game_test tests/player_scaler_test tests/player_core_test tests/launcher_directory_test tests/launcher_navigation_test tests/launcher_thumbnail_test tests/launcher_context_test tests/emu_cache_test tests/res_cache_test tests/render_common_test tests/integration_workflows_test tests/log_test tests/frame_pacer_test @@ -257,12 +258,12 @@ tests/log_test: tests/unit/all/common/test_log.c workspace/all/common/log.c $(TE @$(CC) $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE -DENABLE_INFO_LOGS -DENABLE_DEBUG_LOGS -o $@ $^ -lpthread # Build comprehensive utils tests -tests/utils_test: tests/unit/all/common/test_utils.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/utils_test: tests/unit/all/common/test_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building comprehensive utils tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build No-Intro name parser tests -tests/nointro_parser_test: tests/unit/all/common/test_nointro_parser.c workspace/all/common/nointro_parser.c workspace/all/common/utils.c workspace/all/common/log.c $(TEST_UNITY) +tests/nointro_parser_test: tests/unit/all/common/test_nointro_parser.c workspace/all/common/nointro_parser.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/log.c $(TEST_UNITY) @echo "Building No-Intro name parser tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) @@ -272,7 +273,7 @@ tests/pad_test: tests/unit/all/common/test_api_pad.c workspace/all/common/pad.c @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build GFX text utility tests (uses fff for TTF mocking) -tests/gfx_text_test: tests/unit/all/common/test_gfx_text.c workspace/all/common/gfx_text.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/sdl_fakes.c $(TEST_UNITY) +tests/gfx_text_test: tests/unit/all/common/test_gfx_text.c workspace/all/common/gfx_text.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/sdl_fakes.c $(TEST_UNITY) @echo "Building GFX text utility tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) -I tests/vendor/fff $(TEST_CFLAGS) -DUNIT_TEST_BUILD @@ -282,52 +283,52 @@ tests/audio_resampler_test: tests/unit/all/common/test_audio_resampler.c workspa @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build Player path generation tests (pure sprintf logic) -tests/player_paths_test: tests/unit/all/player/test_player_paths.c workspace/all/player/player_paths.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/player_paths_test: tests/unit/all/player/test_player_paths.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building Player path generation tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build LessUI launcher utility tests (pure string logic) -tests/launcher_utils_test: tests/unit/all/launcher/test_launcher_utils.c workspace/all/launcher/launcher_utils.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/launcher_utils_test: tests/unit/all/launcher/test_launcher_utils.c workspace/all/launcher/launcher_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building LessUI launcher utility tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build M3U parser tests (uses file mocking with GCC --wrap, Docker-only) -tests/m3u_parser_test: tests/unit/all/launcher/test_m3u_parser.c workspace/all/launcher/launcher_m3u.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) +tests/m3u_parser_test: tests/unit/all/launcher/test_m3u_parser.c workspace/all/launcher/launcher_m3u.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) @echo "Building M3U parser tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets # Build LessUI file utility tests (uses file mocking with GCC --wrap, Docker-only) -tests/launcher_file_utils_test: tests/unit/all/launcher/test_launcher_file_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) +tests/launcher_file_utils_test: tests/unit/all/launcher/test_launcher_file_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) @echo "Building LessUI file utility tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets # Build map.txt parser tests (uses file mocking with GCC --wrap, Docker-only) -tests/map_parser_test: tests/unit/all/launcher/test_map_parser.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) +tests/map_parser_test: tests/unit/all/launcher/test_map_parser.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) @echo "Building map.txt parser tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=access -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets # Build collection parser tests (uses file mocking with GCC --wrap, Docker-only) -tests/collection_parser_test: tests/unit/all/launcher/test_collection_parser.c workspace/all/launcher/collection_parser.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) +tests/collection_parser_test: tests/unit/all/launcher/test_collection_parser.c workspace/all/launcher/collection_parser.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) @echo "Building collection parser tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets # Build recent.txt file tests (uses file mocking with GCC --wrap, Docker-only) -tests/recent_parser_test: tests/unit/all/launcher/test_recent_parser.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) +tests/recent_parser_test: tests/unit/all/launcher/test_recent_parser.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/fs_mocks.c $(TEST_UNITY) @echo "Building recent.txt parser tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L -Wl,--wrap=exists -Wl,--wrap=fopen -Wl,--wrap=fclose -Wl,--wrap=fgets # Build recent.txt writer tests (uses real temp files, no --wrap needed) -tests/recent_writer_test: tests/unit/all/launcher/test_recent_writer.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/test_temp.c $(TEST_UNITY) +tests/recent_writer_test: tests/unit/all/launcher/test_recent_writer.c workspace/all/launcher/recent_file.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/test_temp.c $(TEST_UNITY) @echo "Building recent.txt writer tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_GNU_SOURCE # Build recent runtime tests (Recent struct and array operations) -tests/recent_runtime_test: tests/unit/all/launcher/test_recent_runtime.c workspace/all/launcher/recent_file.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/recent_runtime_test: tests/unit/all/launcher/test_recent_runtime.c workspace/all/launcher/recent_file.c workspace/all/common/stb_ds_impl.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building recent runtime tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L # Build directory utility tests (uses real temp directories, no --wrap needed) -tests/directory_utils_test: tests/unit/all/launcher/test_directory_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/directory_utils_test: tests/unit/all/launcher/test_directory_utils.c workspace/all/launcher/launcher_file_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building directory utility tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE @@ -358,12 +359,12 @@ tests/effect_generate_test: tests/unit/all/common/test_effect_generate.c $(TEST_ @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build player utility tests (pure functions extracted from player.c) -tests/player_utils_test: tests/unit/all/player/test_player_utils.c workspace/all/player/player_utils.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/player_utils_test: tests/unit/all/player/test_player_utils.c workspace/all/player/player_utils.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building player utility tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build player configuration tests (config path and option utilities) -tests/player_config_test: tests/unit/all/player/test_player_config.c workspace/all/player/player_config.c workspace/all/player/player_paths.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/player_config_test: tests/unit/all/player/test_player_config.c workspace/all/player/player_config.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building player configuration tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) @@ -378,17 +379,17 @@ tests/platform_variant_test: tests/unit/all/common/test_platform_variant.c works @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) # Build Launcher entry type tests (Entry struct and array operations) -tests/launcher_entry_test: tests/unit/all/launcher/test_launcher_entry.c workspace/all/launcher/launcher_entry.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/launcher_entry_test: tests/unit/all/launcher/test_launcher_entry.c workspace/all/launcher/launcher_entry.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building Launcher entry tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L # Build directory indexing tests (alias application, filtering, duplicate detection) -tests/directory_index_test: tests/unit/all/launcher/test_directory_index.c workspace/all/launcher/directory_index.c workspace/all/launcher/launcher_entry.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/directory_index_test: tests/unit/all/launcher/test_directory_index.c workspace/all/launcher/directory_index.c workspace/all/launcher/launcher_entry.c workspace/all/launcher/launcher_map.c workspace/all/common/stb_ds_impl.c workspace/all/launcher/launcher_str_compare.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building directory indexing tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L # Build archive extraction tests (uses 7z binary via system()) -tests/player_archive_test: tests/unit/all/player/test_player_archive.c workspace/all/player/player_archive.c workspace/all/player/player_game.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) +tests/player_archive_test: tests/unit/all/player/test_player_archive.c workspace/all/player/player_archive.c workspace/all/player/player_game.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c $(TEST_UNITY) @echo "Building archive extraction tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_POSIX_C_SOURCE=200809L @@ -398,7 +399,7 @@ tests/player_memory_test: tests/unit/all/player/test_player_memory.c workspace/a @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_GNU_SOURCE # Build save state system tests (with mock core callbacks, uses real temp files) -tests/player_state_test: tests/unit/all/player/test_player_state.c workspace/all/player/player_state.c workspace/all/player/player_paths.c workspace/all/common/utils.c workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/libretro_mocks.c $(TEST_UNITY) +tests/player_state_test: tests/unit/all/player/test_player_state.c workspace/all/player/player_state.c workspace/all/player/player_paths.c workspace/all/common/utils.c $(PATHS_STUB) workspace/all/common/nointro_parser.c workspace/all/common/log.c tests/support/libretro_mocks.c $(TEST_UNITY) @echo "Building save state system tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE @@ -434,7 +435,7 @@ tests/player_menu_test: tests/unit/all/player/test_player_menu.c \ workspace/all/player/player_context.c \ tests/support/menu_state_stub.c \ tests/support/sdl_fakes.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -444,7 +445,7 @@ tests/player_menu_test: tests/unit/all/player/test_player_menu.c \ # Build env callback tests (tests environment callback handlers in player_env.c) tests/player_env_test: tests/unit/all/player/test_player_env.c \ workspace/all/player/player_env.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -454,7 +455,7 @@ tests/player_env_test: tests/unit/all/player/test_player_env.c \ # Build game file handling tests (ZIP parsing, M3U detection, extension matching) tests/player_game_test: tests/unit/all/player/test_player_game.c \ workspace/all/player/player_game.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -482,7 +483,7 @@ tests/launcher_directory_test: tests/unit/all/launcher/test_launcher_directory.c workspace/all/launcher/launcher_entry.c \ workspace/all/launcher/launcher_str_compare.c \ workspace/all/common/stb_ds_impl.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -497,7 +498,7 @@ tests/launcher_navigation_test: tests/unit/all/launcher/test_launcher_navigation workspace/all/launcher/launcher_entry.c \ workspace/all/launcher/launcher_str_compare.c \ workspace/all/common/stb_ds_impl.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -522,7 +523,7 @@ tests/launcher_context_test: tests/unit/all/launcher/test_launcher_context.c \ tests/emu_cache_test: tests/unit/all/launcher/test_emu_cache.c \ workspace/all/launcher/launcher_emu_cache.c \ workspace/all/common/stb_ds_impl.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -533,7 +534,7 @@ tests/emu_cache_test: tests/unit/all/launcher/test_emu_cache.c \ tests/res_cache_test: tests/unit/all/launcher/test_res_cache.c \ workspace/all/launcher/launcher_res_cache.c \ workspace/all/common/stb_ds_impl.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) @@ -558,7 +559,7 @@ tests/integration_workflows_test: tests/integration/test_workflows.c \ workspace/all/common/binary_file_utils.c \ workspace/all/common/stb_ds_impl.c \ workspace/all/player/player_paths.c \ - workspace/all/common/utils.c \ + workspace/all/common/utils.c $(PATHS_STUB) \ workspace/all/common/nointro_parser.c \ workspace/all/common/log.c \ $(TEST_UNITY) diff --git a/skeleton/BOOT/common/updater b/skeleton/BOOT/common/updater index 92f4ba55..8977f93c 100755 --- a/skeleton/BOOT/common/updater +++ b/skeleton/BOOT/common/updater @@ -2,35 +2,44 @@ # NOTE: becomes .tmp_update/updater -INFO=`cat /proc/cpuinfo 2> /dev/null` -case $INFO in -*"sun8i"*) - if [ -d /usr/miyoo ]; then - PLATFORM="my282" # Miyoo A30 - else - PLATFORM="trimuismart" - fi - ;; -*"SStar"*) - PLATFORM="miyoomini" - ;; -*"TG5040"*|*"TG3040"*) - PLATFORM="tg5040" # Trimui Smart Pro or Brick - ;; -*"0xd03"*) - PLATFORM="zero28" # MagicX Mini Zero 28 - ;; -*"0xd05"*) - PLATFORM="my355" # Miyoo Flip - ;; -esac +# LessOS provides platform directly via environment variable +if [ -n "$LESSOS_PLATFORM" ]; then + PLATFORM=$(echo "$LESSOS_PLATFORM" | tr '[:upper:]' '[:lower:]') +else + # Stock OS - detect platform from cpuinfo + INFO=$(cat /proc/cpuinfo 2>/dev/null) + case $INFO in + *"sun8i"*) + if [ -d /usr/miyoo ]; then + PLATFORM="my282" # Miyoo A30 + else + PLATFORM="trimuismart" + fi + ;; + *"SStar"*) + PLATFORM="miyoomini" + ;; + *"TG5040"*|*"TG3040"*) + PLATFORM="tg5040" # Trimui Smart Pro or Brick + ;; + *"0xd03"*) + PLATFORM="zero28" # MagicX Mini Zero 28 + ;; + *"0xd05"*) + PLATFORM="my355" # Miyoo Flip + ;; + esac -# fallback for tg5040 20240413 recovery firmware -# TODO: doublecheck interaction with tg3040 -# might need/want to strings /usr/trimui/bin/MainUI during install/update -# and store platform in a text file -if [ -z "$PLATFORM" ] && [ -f /usr/trimui/bin/runtrimui.sh ]; then - PLATFORM="tg5040" + # fallback for tg5040 20240413 recovery firmware + # TODO: doublecheck interaction with tg3040 + # might need/want to strings /usr/trimui/bin/MainUI during install/update + # and store platform in a text file + if [ -z "$PLATFORM" ] && [ -f /usr/trimui/bin/runtrimui.sh ]; then + PLATFORM="tg5040" + fi fi -exec "/mnt/SDCARD/.tmp_update/$PLATFORM.sh" # &> /mnt/SDCARD/boot.txt +# Use LessOS storage path if set, otherwise default to stock path +SDCARD_PATH="${LESSOS_STORAGE:-/mnt/SDCARD}" + +exec "$SDCARD_PATH/.tmp_update/$PLATFORM.sh" # &> $SDCARD_PATH/boot.txt diff --git a/skeleton/BOOT/lessos/init.sh b/skeleton/BOOT/lessos/init.sh new file mode 100755 index 00000000..db7f208c --- /dev/null +++ b/skeleton/BOOT/lessos/init.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# LessOS Bootstrap for LessUI +# Mirrors stock platform hijack scripts (miyoomini.sh, etc.) +# +# LessOS provides these environment variables before calling this script: +# LESSOS_PLATFORM - SoC/platform family (e.g., "RK3566") +# LESSOS_DEVICE - Specific device model (e.g., "Powkiddy RGB30") +# LESSOS_STORAGE - Writable storage path (e.g., "/storage") + +SDCARD_PATH="${LESSOS_STORAGE:-/storage}" +SCRIPT_DIR="$(dirname "$0")" + +# Copy .tmp_update to SD card (like miyoomini.sh does) +# This ensures the updater and platform boot scripts are in place +if [ -d "$SCRIPT_DIR/.tmp_update" ]; then + cp -rf "$SCRIPT_DIR/.tmp_update" "$SDCARD_PATH/" + sync +fi + +# Run the updater (handles platform detection, updates, and launching) +"$SDCARD_PATH/.tmp_update/updater" + +# Fallback if updater exits unexpectedly +reboot diff --git a/skeleton/SYSTEM/rk3566/bin/shutdown b/skeleton/SYSTEM/rk3566/bin/shutdown new file mode 100755 index 00000000..46ebe7c2 --- /dev/null +++ b/skeleton/SYSTEM/rk3566/bin/shutdown @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ -n "$DATETIME_PATH" ]; then + echo `date +'%F %T'` > "$DATETIME_PATH" + sync +fi + +cat /dev/zero > /dev/fb0 +poweroff +# echo s > /proc/sysrq-trigger +# echo u > /proc/sysrq-trigger +# echo o > /proc/sysrq-trigger diff --git a/skeleton/SYSTEM/rk3566/cores/.keep b/skeleton/SYSTEM/rk3566/cores/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/rk3566/dat/.keep b/skeleton/SYSTEM/rk3566/dat/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/rk3566/lib/.keep b/skeleton/SYSTEM/rk3566/lib/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/rk3566/system.cfg b/skeleton/SYSTEM/rk3566/system.cfg new file mode 100644 index 00000000..f2e1550f --- /dev/null +++ b/skeleton/SYSTEM/rk3566/system.cfg @@ -0,0 +1,2 @@ +-player_screen_sharpness = Crisp + diff --git a/tests/support/paths_stub.c b/tests/support/paths_stub.c new file mode 100644 index 00000000..d16a32c8 --- /dev/null +++ b/tests/support/paths_stub.c @@ -0,0 +1,36 @@ +/** + * paths_stub.c - Test stub for runtime paths + * + * Provides initialized path globals for unit testing. + * Uses values from tests/support/platform.h (SDCARD_PATH="/tmp/test") + */ + +#include "paths.h" + +#include + +// Initialize paths with test values +char g_sdcard_path[PATHS_MAX_LEN] = "/tmp/test"; +char g_roms_path[PATHS_MAX_LEN] = "/tmp/test/Roms"; +char g_root_system_path[PATHS_MAX_LEN] = "/tmp/test/.system"; +char g_system_path[PATHS_MAX_LEN] = "/tmp/test/.system/test"; +char g_res_path[PATHS_MAX_LEN] = "/tmp/test/.system/res"; +char g_font_path[PATHS_MAX_LEN] = "/tmp/test/.system/res/font.ttf"; +char g_userdata_path[PATHS_MAX_LEN] = "/tmp/test/.userdata/test"; +char g_shared_userdata_path[PATHS_MAX_LEN] = "/tmp/test/.userdata/shared"; +char g_paks_path[PATHS_MAX_LEN] = "/tmp/test/.system/test/paks"; +char g_recent_path[PATHS_MAX_LEN] = "/tmp/test/.userdata/shared/.launcher/recent.txt"; +char g_simple_mode_path[PATHS_MAX_LEN] = "/tmp/test/.userdata/test/.simple_mode"; +char g_auto_resume_path[PATHS_MAX_LEN] = "/tmp/test/.userdata/test/.auto_resume"; +char g_faux_recent_path[PATHS_MAX_LEN] = "/tmp/test/Recently Played"; +char g_collections_path[PATHS_MAX_LEN] = "/tmp/test/Collections"; + +static int s_initialized = 1; // Pre-initialized for tests + +void Paths_init(void) { + // No-op for tests - already initialized with test values +} + +int Paths_isInitialized(void) { + return s_initialized; +} diff --git a/toolchains.json b/toolchains.json index 28f35342..d1795ee3 100644 --- a/toolchains.json +++ b/toolchains.json @@ -5,6 +5,9 @@ }, "tg5040": { "platform": "linux/amd64" + }, + "rk3566": { + "platform": "linux/amd64" } } } diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 10b361bf..d77af03c 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -44,6 +44,7 @@ #include "defines.h" #include "gfx_text.h" #include "pad.h" +#include "paths.h" #include "utils.h" /////////////////////////////// @@ -364,7 +365,7 @@ SDL_Surface* GFX_init(int mode) { // Load asset sprite sheet at selected tier char asset_path[MAX_PATH]; - (void)snprintf(asset_path, sizeof(asset_path), RES_PATH "/assets@%ix.png", asset_scale); + (void)snprintf(asset_path, sizeof(asset_path), "%s/assets@%ix.png", g_res_path, asset_scale); LOG_debug("GFX_init: Loading assets from: %s", asset_path); if (!exists(asset_path)) LOG_error("GFX_init: Missing assets at %s, about to segfault!", asset_path); @@ -582,17 +583,17 @@ SDL_Surface* GFX_init(int mode) { LOG_error("GFX_init: TTF_Init failed: %s", SDL_GetError()); return NULL; } - LOG_debug("GFX_init: Loading fonts from %s", FONT_PATH); - font.large = TTF_OpenFont(FONT_PATH, DP(FONT_LARGE)); + LOG_debug("GFX_init: Loading fonts from %s", g_font_path); + font.large = TTF_OpenFont(g_font_path, DP(FONT_LARGE)); if (!font.large) LOG_error("GFX_init: Failed to load large font: %s", SDL_GetError()); - font.medium = TTF_OpenFont(FONT_PATH, DP(FONT_MEDIUM)); + font.medium = TTF_OpenFont(g_font_path, DP(FONT_MEDIUM)); if (!font.medium) LOG_error("GFX_init: Failed to load medium font: %s", SDL_GetError()); - font.small = TTF_OpenFont(FONT_PATH, DP(FONT_SMALL)); + font.small = TTF_OpenFont(g_font_path, DP(FONT_SMALL)); if (!font.small) LOG_error("GFX_init: Failed to load small font: %s", SDL_GetError()); - font.tiny = TTF_OpenFont(FONT_PATH, DP(FONT_TINY)); + font.tiny = TTF_OpenFont(g_font_path, DP(FONT_TINY)); if (!font.tiny) LOG_error("GFX_init: Failed to load tiny font: %s", SDL_GetError()); LOG_debug("GFX_init: Fonts loaded successfully"); @@ -3029,9 +3030,10 @@ void PWR_powerOff(void) { char* msg; if (HAS_POWER_BUTTON || HAS_POWEROFF_BUTTON) - msg = exists(AUTO_RESUME_PATH) ? "Quicksave created,\npowering off" : "Powering off"; + msg = exists(g_auto_resume_path) ? "Quicksave created,\npowering off" : "Powering off"; else - msg = exists(AUTO_RESUME_PATH) ? "Quicksave created,\npower off now" : "Power off now"; + msg = + exists(g_auto_resume_path) ? "Quicksave created,\npower off now" : "Power off now"; // LOG_info("PWR_powerOff %s (%ix%i)\n", gfx.screen, gfx.screen->w, gfx.screen->h); diff --git a/workspace/all/common/build.mk b/workspace/all/common/build.mk index a95e9a66..b78f4383 100644 --- a/workspace/all/common/build.mk +++ b/workspace/all/common/build.mk @@ -80,6 +80,7 @@ COMMON_SOURCE = \ $(COMMON_DIR)/gfx_text.c \ $(COMMON_DIR)/scaler.c \ $(COMMON_DIR)/platform_variant.c \ + $(COMMON_DIR)/paths.c \ $(PLATFORM_DIR)/platform.c # Add shared rendering modules diff --git a/workspace/all/common/paths.c b/workspace/all/common/paths.c new file mode 100644 index 00000000..384ca9ad --- /dev/null +++ b/workspace/all/common/paths.c @@ -0,0 +1,77 @@ +/** + * paths.c - Runtime path resolution for dynamic storage locations + * + * Initializes all runtime paths based on environment variables or defaults. + */ + +#include "paths.h" +#include "log.h" +#include "platform.h" +#include +#include +#include + +// Runtime path storage +char g_sdcard_path[PATHS_MAX_LEN]; +char g_roms_path[PATHS_MAX_LEN]; +char g_root_system_path[PATHS_MAX_LEN]; +char g_system_path[PATHS_MAX_LEN]; +char g_res_path[PATHS_MAX_LEN]; +char g_font_path[PATHS_MAX_LEN]; +char g_userdata_path[PATHS_MAX_LEN]; +char g_shared_userdata_path[PATHS_MAX_LEN]; +char g_paks_path[PATHS_MAX_LEN]; +char g_recent_path[PATHS_MAX_LEN]; +char g_simple_mode_path[PATHS_MAX_LEN]; +char g_auto_resume_path[PATHS_MAX_LEN]; +char g_faux_recent_path[PATHS_MAX_LEN]; +char g_collections_path[PATHS_MAX_LEN]; + +static int s_initialized = 0; + +void Paths_init(void) { + if (s_initialized) { + return; + } + + // Check for LessOS storage environment variable + const char* env_storage = getenv("LESSOS_STORAGE"); + if (env_storage && env_storage[0] != '\0') { + strncpy(g_sdcard_path, env_storage, PATHS_MAX_LEN - 1); + g_sdcard_path[PATHS_MAX_LEN - 1] = '\0'; + LOG_info("Paths_init: Using LESSOS_STORAGE=%s", g_sdcard_path); + } else { + // Fall back to compile-time default + strncpy(g_sdcard_path, SDCARD_PATH, PATHS_MAX_LEN - 1); + g_sdcard_path[PATHS_MAX_LEN - 1] = '\0'; + LOG_info("Paths_init: Using default SDCARD_PATH=%s", g_sdcard_path); + } + + // Build all derived paths + (void)snprintf(g_roms_path, PATHS_MAX_LEN, "%s/Roms", g_sdcard_path); + (void)snprintf(g_root_system_path, PATHS_MAX_LEN, "%s/.system/", g_sdcard_path); + (void)snprintf(g_system_path, PATHS_MAX_LEN, "%s/.system/%s", g_sdcard_path, PLATFORM); + (void)snprintf(g_res_path, PATHS_MAX_LEN, "%s/.system/res", g_sdcard_path); + (void)snprintf(g_font_path, PATHS_MAX_LEN, "%s/.system/res/InterTight-Bold.ttf", g_sdcard_path); + (void)snprintf(g_userdata_path, PATHS_MAX_LEN, "%s/.userdata/%s", g_sdcard_path, PLATFORM); + (void)snprintf(g_shared_userdata_path, PATHS_MAX_LEN, "%s/.userdata/shared", g_sdcard_path); + (void)snprintf(g_paks_path, PATHS_MAX_LEN, "%s/paks", g_system_path); + (void)snprintf(g_recent_path, PATHS_MAX_LEN, "%s/.launcher/recent.txt", g_shared_userdata_path); + (void)snprintf(g_simple_mode_path, PATHS_MAX_LEN, "%s/enable-simple-mode", + g_shared_userdata_path); + (void)snprintf(g_auto_resume_path, PATHS_MAX_LEN, "%s/.launcher/auto_resume.txt", + g_shared_userdata_path); + (void)snprintf(g_faux_recent_path, PATHS_MAX_LEN, "%s/Recently Played", g_sdcard_path); + (void)snprintf(g_collections_path, PATHS_MAX_LEN, "%s/Collections", g_sdcard_path); + + LOG_debug("Paths_init: g_roms_path=%s", g_roms_path); + LOG_debug("Paths_init: g_system_path=%s", g_system_path); + LOG_debug("Paths_init: g_res_path=%s", g_res_path); + LOG_debug("Paths_init: g_paks_path=%s", g_paks_path); + + s_initialized = 1; +} + +int Paths_isInitialized(void) { + return s_initialized; +} diff --git a/workspace/all/common/paths.h b/workspace/all/common/paths.h new file mode 100644 index 00000000..44df26b2 --- /dev/null +++ b/workspace/all/common/paths.h @@ -0,0 +1,56 @@ +/** + * paths.h - Runtime path resolution for dynamic storage locations + * + * This module provides runtime-resolved paths that can adapt to different + * storage locations (e.g., LessOS internal vs external storage). + * + * Call Paths_init() early in main() before using any path variables. + * The paths will use the LESSOS_STORAGE environment variable if set, + * otherwise fall back to the compile-time SDCARD_PATH default. + */ + +#ifndef PATHS_H +#define PATHS_H + +#include "defines.h" + +// Maximum path length for runtime paths +#define PATHS_MAX_LEN MAX_PATH + +/** + * Runtime path variables. + * These mirror the compile-time macros in defines.h but are resolved at runtime. + */ +extern char g_sdcard_path[PATHS_MAX_LEN]; +extern char g_roms_path[PATHS_MAX_LEN]; +extern char g_root_system_path[PATHS_MAX_LEN]; +extern char g_system_path[PATHS_MAX_LEN]; +extern char g_res_path[PATHS_MAX_LEN]; +extern char g_font_path[PATHS_MAX_LEN]; +extern char g_userdata_path[PATHS_MAX_LEN]; +extern char g_shared_userdata_path[PATHS_MAX_LEN]; +extern char g_paks_path[PATHS_MAX_LEN]; +extern char g_recent_path[PATHS_MAX_LEN]; +extern char g_simple_mode_path[PATHS_MAX_LEN]; +extern char g_auto_resume_path[PATHS_MAX_LEN]; +extern char g_faux_recent_path[PATHS_MAX_LEN]; +extern char g_collections_path[PATHS_MAX_LEN]; + +/** + * Initialize runtime paths. + * + * Reads the LESSOS_STORAGE environment variable if set, otherwise uses + * the compile-time SDCARD_PATH default. All derived paths are then built. + * + * Must be called early in main() before any paths are used. + */ +void Paths_init(void); + +/** + * Check if paths have been initialized. + * + * @return 1 if Paths_init() has been called, 0 otherwise + */ +int Paths_isInitialized(void); + +#endif // PATHS_H diff --git a/workspace/all/common/utils.c b/workspace/all/common/utils.c index 83c4d877..bf77fce3 100644 --- a/workspace/all/common/utils.c +++ b/workspace/all/common/utils.c @@ -11,6 +11,7 @@ #include "defines.h" #include "log.h" #include "nointro_parser.h" +#include "paths.h" #include #include #include @@ -268,7 +269,7 @@ int findSystemFile(const char* relative_path, char* output_path) { char candidate[512]; // Check platform-specific location first - (void)snprintf(candidate, sizeof(candidate), "%s/.system/%s/%s", SDCARD_PATH, PLATFORM, + (void)snprintf(candidate, sizeof(candidate), "%s/%s/%s", g_root_system_path, PLATFORM, relative_path); if (exists(candidate)) { safe_strcpy(output_path, candidate, MAX_PATH); @@ -276,8 +277,7 @@ int findSystemFile(const char* relative_path, char* output_path) { } // Fall back to shared common location - (void)snprintf(candidate, sizeof(candidate), "%s/.system/common/%s", SDCARD_PATH, - relative_path); + (void)snprintf(candidate, sizeof(candidate), "%s/common/%s", g_root_system_path, relative_path); if (exists(candidate)) { safe_strcpy(output_path, candidate, MAX_PATH); return 1; @@ -563,8 +563,8 @@ void getEmuName(const char* in_name, char* out_name) { tmp = out_name; // Extract just the Roms folder name if this is a full path - if (prefixMatch(ROMS_PATH, tmp)) { - tmp += strlen(ROMS_PATH) + 1; + if (prefixMatch(g_roms_path, tmp)) { + tmp += strlen(g_roms_path) + 1; char* tmp2 = strchr(tmp, '/'); if (tmp2) tmp2[0] = '\0'; @@ -600,11 +600,11 @@ void getEmuName(const char* in_name, char* out_name) { * @note pak_path is always modified, even if file doesn't exist */ void getEmuPath(char* emu_name, char* pak_path) { - (void)snprintf(pak_path, MAX_PATH, "%s/Emus/%s/%s.pak/launch.sh", SDCARD_PATH, PLATFORM, + (void)snprintf(pak_path, MAX_PATH, "%s/Emus/%s/%s.pak/launch.sh", g_sdcard_path, PLATFORM, emu_name); if (exists(pak_path)) return; - (void)snprintf(pak_path, MAX_PATH, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name); + (void)snprintf(pak_path, MAX_PATH, "%s/Emus/%s.pak/launch.sh", g_paks_path, emu_name); } /////////////////////////////// diff --git a/workspace/all/launcher/Makefile b/workspace/all/launcher/Makefile index 4829c330..adef077b 100644 --- a/workspace/all/launcher/Makefile +++ b/workspace/all/launcher/Makefile @@ -23,7 +23,7 @@ SDL ?= SDL TARGET = launcher INCDIR = -I. -I../common/ -I../player/libretro-common/include/ -isystem ../vendor/stb/ -I../../$(PLATFORM)/platform/ SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/nointro_parser.c \ - ../common/api.c ../common/ui_layout.c ../common/log.c ../common/pad.c \ + ../common/api.c ../common/ui_layout.c ../common/log.c ../common/pad.c ../common/paths.c \ ../common/gfx_text.c ../common/platform_variant.c ../common/stb_ds_impl.c \ launcher_entry.c launcher_launcher.c directory_index.c launcher_str_compare.c \ launcher_state.c launcher_m3u.c launcher_map.c launcher_file_utils.c launcher_directory.c \ diff --git a/workspace/all/launcher/launcher.c b/workspace/all/launcher/launcher.c index 65d0abe8..18c68247 100644 --- a/workspace/all/launcher/launcher.c +++ b/workspace/all/launcher/launcher.c @@ -56,6 +56,7 @@ #include "launcher_state.h" #include "launcher_str_compare.h" #include "launcher_thumbnail.h" +#include "paths.h" #include "recent_file.h" #include "utils.h" @@ -360,7 +361,7 @@ static void thumb_cache_clear(LauncherThumbnailCache* cache) { * * Map.txt format: Each line is "filenamedisplay name" * - If display name starts with '.', the entry is hidden - * - Collections use a shared map.txt in COLLECTIONS_PATH + * - Collections use a shared map.txt in g_collections_path * * Duplicate handling: * - If two entries have the same display name but different filenames, @@ -371,15 +372,16 @@ static void thumb_cache_clear(LauncherThumbnailCache* cache) { * @param self Directory to index (modified in place) */ void Directory_index(Directory* self) { - int is_collection = prefixMatch(COLLECTIONS_PATH, self->path); - int skip_index = exactMatch(FAUX_RECENT_PATH, self->path) || is_collection; // not alphabetized + int is_collection = prefixMatch(g_collections_path, self->path); + int skip_index = + exactMatch(g_faux_recent_path, self->path) || is_collection; // not alphabetized // Load maps for name aliasing (pak-bundled + user overrides) // For collections, just load collection map.txt directly MapEntry* map; if (is_collection) { char map_path[256]; - (void)snprintf(map_path, sizeof(map_path), "%s/map.txt", COLLECTIONS_PATH); + (void)snprintf(map_path, sizeof(map_path), "%s/map.txt", g_collections_path); map = Map_load(map_path); } else { // Load merged pak + user maps for ROM directories @@ -411,7 +413,7 @@ static Entry** getEntries(char* path); * Automatically determines which type of directory this is and * populates its entries accordingly: * - Root (SDCARD_PATH): Shows systems, recents, collections, tools - * - Recently played (FAUX_RECENT_PATH): Shows recent games + * - Recently played (g_faux_recent_path): Shows recent games * - Collection (.txt file): Loads games from text file * - Multi-disc (.m3u file): Shows disc list * - Regular directory: Shows files and subdirectories @@ -440,11 +442,11 @@ Directory* Directory_new(char* path, int selected) { free(self); return NULL; } - if (exactMatch(path, SDCARD_PATH)) { + if (exactMatch(path, g_sdcard_path)) { self->entries = getRoot(); - } else if (exactMatch(path, FAUX_RECENT_PATH)) { + } else if (exactMatch(path, g_faux_recent_path)) { self->entries = getRecents(); - } else if (!exactMatch(path, COLLECTIONS_PATH) && prefixMatch(COLLECTIONS_PATH, path) && + } else if (!exactMatch(path, g_collections_path) && prefixMatch(g_collections_path, path) && suffixMatch(".txt", path)) { self->entries = getCollection(path); } else if (suffixMatch(".m3u", path)) { @@ -483,7 +485,7 @@ static int hasEmu(char* emu_name); // Local wrappers for convenience: static Recent* Recent_new_local(char* path, char* alias) { - return Recent_new(path, alias, SDCARD_PATH, hasEmu); + return Recent_new(path, alias, g_sdcard_path, hasEmu); } /////////////////////////////// @@ -524,9 +526,9 @@ static LauncherRestoreState g_restore_state = { * Paths are relative to SDCARD_PATH for platform portability. */ static void saveRecents(void) { - FILE* file = fopen(RECENT_PATH, "w"); + FILE* file = fopen(g_recent_path, "w"); if (!file) { - LOG_errno("Failed to save recent games to %s", RECENT_PATH); + LOG_errno("Failed to save recent games to %s", g_recent_path); return; } @@ -554,7 +556,7 @@ static void saveRecents(void) { * @param alias Optional custom display name, or NULL */ static void addRecent(char* path, char* alias) { - path += strlen(SDCARD_PATH); // makes paths platform agnostic + path += strlen(g_sdcard_path); // makes paths platform agnostic int id = RecentArray_indexOf(recents, path); if (id == -1) { // add new entry while ((int)arrlen(recents) >= MAX_RECENTS) { @@ -609,7 +611,7 @@ static int hasM3u(char* rom_path, char* m3u_path) { * * This function performs several important tasks: * 1. Handles disc change requests (from in-game disc swapping) - * 2. Loads recent games from RECENT_PATH file + * 2. Loads recent games from g_recent_path file * 3. Filters out games whose emulators no longer exist * 4. Deduplicates multi-disc games (shows only most recent disc) * 5. Populates the global recents array @@ -623,7 +625,7 @@ static int hasM3u(char* rom_path, char* m3u_path) { * @return 1 if any playable recents exist, 0 otherwise */ static int hasRecents(void) { - LOG_debug("hasRecents %s", RECENT_PATH); + LOG_debug("hasRecents %s", g_recent_path); int has = 0; // Track parent directories to avoid duplicate multi-disc entries @@ -632,7 +634,7 @@ static int hasRecents(void) { char sd_path[256]; getFile(CHANGE_DISC_PATH, sd_path, 256); if (exists(sd_path)) { - char* disc_path = sd_path + strlen(SDCARD_PATH); // makes path platform agnostic + char* disc_path = sd_path + strlen(g_sdcard_path); // makes path platform agnostic Recent* recent = Recent_new_local(disc_path, NULL); if (recent) { if (recent->available) @@ -651,7 +653,7 @@ static int hasRecents(void) { unlink(CHANGE_DISC_PATH); } - FILE* file = fopen(RECENT_PATH, "r"); // newest at top + FILE* file = fopen(g_recent_path, "r"); // newest at top if (file) { char line[256]; while (fgets(line, 256, file) != NULL) { @@ -671,7 +673,7 @@ static int hasRecents(void) { } char sd_path[MAX_PATH]; - (void)snprintf(sd_path, sizeof(sd_path), "%s%s", SDCARD_PATH, path); + (void)snprintf(sd_path, sizeof(sd_path), "%s%s", g_sdcard_path, path); if (exists(sd_path)) { if ((int)arrlen(recents) < MAX_RECENTS) { // this logic replaces an existing disc from a multi-disc game with the last used @@ -729,7 +731,7 @@ static int hasRecents(void) { * Wrapper around Launcher_hasNonHiddenFiles. */ static int hasCollections(void) { - return Launcher_hasNonHiddenFiles(COLLECTIONS_PATH); + return Launcher_hasNonHiddenFiles(g_collections_path); } /** @@ -737,7 +739,7 @@ static int hasCollections(void) { * Wrapper around LauncherDir_hasRoms with platform-specific paths. */ static int hasRoms(char* dir_name) { - return LauncherDir_hasRoms(dir_name, ROMS_PATH, PAKS_PATH, SDCARD_PATH, PLATFORM); + return LauncherDir_hasRoms(dir_name, g_roms_path, g_paks_path, g_sdcard_path, PLATFORM); } /////////////////////////////// @@ -761,26 +763,42 @@ static int hasRoms(char* dir_name) { static Entry** getRoot(void) { Entry** root = NULL; + LOG_debug("getRoot: g_roms_path=%s", g_roms_path); + LOG_debug("getRoot: g_paks_path=%s", g_paks_path); + LOG_debug("getRoot: PLATFORM=%s", PLATFORM); + if (hasRecents()) - arrpush(root, Entry_new(FAUX_RECENT_PATH, ENTRY_DIR)); + arrpush(root, Entry_new(g_faux_recent_path, ENTRY_DIR)); Entry** entries = NULL; - DIR* dh = opendir(ROMS_PATH); + DIR* dh = opendir(g_roms_path); if (dh != NULL) { + LOG_debug("getRoot: Opened g_roms_path successfully"); struct dirent* dp; char* tmp; char full_path[256]; - (void)snprintf(full_path, sizeof(full_path), "%s/", ROMS_PATH); + (void)snprintf(full_path, sizeof(full_path), "%s/", g_roms_path); tmp = full_path + strlen(full_path); Entry** emus = NULL; + int dir_count = 0; + int has_roms_count = 0; + int total_entries = 0; while ((dp = readdir(dh)) != NULL) { + total_entries++; + LOG_debug("getRoot: readdir entry='%s' d_type=%d", dp->d_name, dp->d_type); if (hide(dp->d_name)) continue; - if (hasRoms(dp->d_name)) { + dir_count++; + int has = hasRoms(dp->d_name); + LOG_debug("getRoot: dir='%s' hasRoms=%d", dp->d_name, has); + if (has) { + has_roms_count++; safe_strcpy(tmp, dp->d_name, sizeof(full_path) - (tmp - full_path)); arrpush(emus, Entry_new(full_path, ENTRY_DIR)); } } + LOG_debug("getRoot: readdir returned %d entries, %d visible dirs, %d have ROMs", + total_entries, dir_count, has_roms_count); EntryArray_sort(emus); Entry* prev_entry = NULL; int emus_count = (int)arrlen(emus); @@ -797,11 +815,15 @@ static Entry** getRoot(void) { } arrfree(emus); // just free the array part, entries now owns emus entries closedir(dh); + } else { + LOG_error("getRoot: Failed to open g_roms_path '%s'", g_roms_path); } + LOG_debug("getRoot: Found %d system entries", (int)arrlen(entries)); + // Apply aliases from Roms/map.txt (we don't support hidden remaps here) char map_path[256]; - (void)snprintf(map_path, sizeof(map_path), "%s/map.txt", ROMS_PATH); + (void)snprintf(map_path, sizeof(map_path), "%s/map.txt", g_roms_path); if (arrlen(entries) > 0) { MapEntry* map = Map_load(map_path); if (map) { @@ -824,14 +846,14 @@ static Entry** getRoot(void) { if (hasCollections()) { if (arrlen(entries)) - arrpush(root, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); + arrpush(root, Entry_new(g_collections_path, ENTRY_DIR)); else { // no visible systems, promote collections to root - dh = opendir(COLLECTIONS_PATH); + dh = opendir(g_collections_path); if (dh != NULL) { struct dirent* dp; char* tmp; char full_path[256]; - (void)snprintf(full_path, sizeof(full_path), "%s/", COLLECTIONS_PATH); + (void)snprintf(full_path, sizeof(full_path), "%s/", g_collections_path); tmp = full_path + strlen(full_path); Entry** collections = NULL; while ((dp = readdir(dh)) != NULL) { @@ -861,7 +883,8 @@ static Entry** getRoot(void) { } arrfree(entries); // root now owns entries' entries - char* tools_path = SDCARD_PATH "/Tools/" PLATFORM; + char tools_path[MAX_PATH]; + (void)snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", g_sdcard_path, PLATFORM); if (exists(tools_path) && !simple_mode) arrpush(root, Entry_new(tools_path, ENTRY_DIR)); @@ -885,7 +908,7 @@ static Entry** getRecents(void) { continue; char sd_path[256]; - (void)snprintf(sd_path, sizeof(sd_path), "%s%s", SDCARD_PATH, recent->path); + (void)snprintf(sd_path, sizeof(sd_path), "%s%s", g_sdcard_path, recent->path); int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; Entry* entry = Entry_new(sd_path, type); if (!entry) @@ -921,7 +944,7 @@ static Entry** getCollection(char* path) { continue; // skip empty lines char sd_path[MAX_PATH]; - (void)snprintf(sd_path, sizeof(sd_path), "%s%s", SDCARD_PATH, line); + (void)snprintf(sd_path, sizeof(sd_path), "%s%s", g_sdcard_path, line); if (exists(sd_path)) { int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; arrpush(entries, Entry_new(sd_path, type)); @@ -979,7 +1002,7 @@ static void addEntries(Entry*** entries, char* path) { continue; safe_strcpy(tmp, dp->d_name, sizeof(full_path) - (tmp - full_path)); int is_dir = dp->d_type == DT_DIR; - int type = LauncherDir_determineEntryType(dp->d_name, is_dir, path, COLLECTIONS_PATH); + int type = LauncherDir_determineEntryType(dp->d_name, is_dir, path, g_collections_path); arrpush(*entries, Entry_new(full_path, type)); } closedir(dh); @@ -991,7 +1014,7 @@ static void addEntries(Entry*** entries, char* path) { * Wrapper around LauncherDir_isConsoleDir. */ static int isConsoleDir(char* path) { - return LauncherDir_isConsoleDir(path, ROMS_PATH); + return LauncherDir_isConsoleDir(path, g_roms_path); } static Entry** getEntries(char* path) { @@ -1001,7 +1024,7 @@ static Entry** getEntries(char* path) { char collation_prefix[LAUNCHER_DIR_MAX_PATH]; if (LauncherDir_buildCollationPrefix(path, collation_prefix)) { // Collated console directory (e.g., "Game Boy (USA)" matches "Game Boy (Japan)") - DIR* dh = opendir(ROMS_PATH); + DIR* dh = opendir(g_roms_path); if (dh != NULL) { struct dirent* dp; char full_path[LAUNCHER_DIR_MAX_PATH]; @@ -1010,7 +1033,7 @@ static Entry** getEntries(char* path) { continue; if (dp->d_type != DT_DIR) continue; - (void)snprintf(full_path, sizeof(full_path), "%s/%s", ROMS_PATH, dp->d_name); + (void)snprintf(full_path, sizeof(full_path), "%s/%s", g_roms_path, dp->d_name); if (!LauncherDir_matchesCollation(full_path, collation_prefix)) continue; @@ -1072,7 +1095,7 @@ static void readyResumePath(char* rom_path, int type) { char path[MAX_PATH]; SAFE_STRCPY(path, rom_path); - if (!prefixMatch(ROMS_PATH, path)) + if (!prefixMatch(g_roms_path, path)) return; char auto_path[MAX_PATH]; @@ -1101,7 +1124,7 @@ static void readyResumePath(char* rom_path, int type) { tmp = strrchr(path, '/') + 1; SAFE_STRCPY(rom_file, tmp); - (void)snprintf(slot_path, sizeof(slot_path), "%s/.launcher/%s/%s.txt", SHARED_USERDATA_PATH, + (void)snprintf(slot_path, sizeof(slot_path), "%s/.launcher/%s/%s.txt", g_shared_userdata_path, emu_name, rom_file); // /.userdata/.launcher//.ext.txt @@ -1117,17 +1140,17 @@ static void loadLast(void); static int autoResume(void) { // NOTE: bypasses recents - if (!exists(AUTO_RESUME_PATH)) + if (!exists(g_auto_resume_path)) return 0; char path[MAX_PATH]; - getFile(AUTO_RESUME_PATH, path, MAX_PATH); - unlink(AUTO_RESUME_PATH); + getFile(g_auto_resume_path, path, MAX_PATH); + unlink(g_auto_resume_path); sync(); // make sure rom still exists char sd_path[MAX_PATH]; - (void)snprintf(sd_path, sizeof(sd_path), "%s%s", SDCARD_PATH, path); + (void)snprintf(sd_path, sizeof(sd_path), "%s%s", g_sdcard_path, path); if (!exists(sd_path)) return 0; @@ -1141,7 +1164,7 @@ static int autoResume(void) { if (!exists(emu_path)) return 0; - // putFile(LAST_PATH, FAUX_RECENT_PATH); // saveLast() will crash here because top is NULL + // putFile(LAST_PATH, g_faux_recent_path); // saveLast() will crash here because top is NULL char cmd[MAX_PATH * 2]; (void)snprintf(cmd, sizeof(cmd), "'%s' '%s'", Launcher_escapeSingleQuotes(emu_path), @@ -1166,7 +1189,7 @@ static int autoResume(void) { */ static void openPak_ctx(LauncherContext* ctx, char* path) { // Save path before escaping (escapeSingleQuotes modifies string) - if (prefixMatch(ROMS_PATH, path)) { + if (prefixMatch(g_roms_path, path)) { if (ctx->callbacks && ctx->callbacks->add_recent) { ctx->callbacks->add_recent(path, NULL); } @@ -1235,7 +1258,7 @@ static void openRom_ctx(LauncherContext* ctx, char* path, char* last) { // get disc for state char disc_path_path[MAX_PATH]; (void)snprintf(disc_path_path, sizeof(disc_path_path), "%s/.launcher/%s/%s.%s.txt", - SHARED_USERDATA_PATH, emu_name, rom_file, + g_shared_userdata_path, emu_name, rom_file, slot); // /.userdata/arm-480/.launcher//.ext.0.txt if (exists(disc_path_path)) { @@ -1407,7 +1430,7 @@ static void Entry_open_ctx(LauncherContext* ctx, Entry* self) { char* last = NULL; char last_path[MAX_PATH]; // Moved outside if block to fix invalidLifetime bug // Collection ROMs use collection path for state restoration - if (prefixMatch(COLLECTIONS_PATH, current_top->path)) { + if (prefixMatch(g_collections_path, current_top->path)) { char* tmp; char filename[MAX_PATH]; @@ -1447,9 +1470,9 @@ static void Entry_open(Entry* self) { */ static void saveLast(char* path) { // special case for recently played - if (exactMatch(top->path, FAUX_RECENT_PATH)) { + if (exactMatch(top->path, g_faux_recent_path)) { // Most recent game is always at top, no need to save specific ROM - path = FAUX_RECENT_PATH; + path = g_faux_recent_path; } putFile(LAST_PATH, path); } @@ -1486,7 +1509,7 @@ static void loadLast(void) { safe_strcpy(filename, tmp, sizeof(filename)); char** last = NULL; - while (!exactMatch(last_path, SDCARD_PATH)) { + while (!exactMatch(last_path, g_sdcard_path)) { char* path_copy = strdup(last_path); if (path_copy) arrpush(last, path_copy); @@ -1499,7 +1522,7 @@ static void loadLast(void) { char* path = arrpop(last); if (!exactMatch( path, - ROMS_PATH)) { // romsDir is effectively root as far as restoring state after a game + g_roms_path)) { // romsDir is effectively root as far as restoring state after a game // Extract collation prefix if this is a collated console dir (e.g., "Game Boy (USA)") // This allows matching against other regions like "Game Boy (Japan)" char collated_path[LAUNCHER_STATE_MAX_PATH]; @@ -1515,7 +1538,7 @@ static void loadLast(void) { // NOTE: strlen() is required for collated_path, '\0' wasn't reading as NULL for some reason if (exactMatch(entry->path, path) || (strlen(collated_path) && prefixMatch(collated_path, entry->path)) || - (prefixMatch(COLLECTIONS_PATH, full_path) && + (prefixMatch(g_collections_path, full_path) && suffixMatch(filename, entry->path))) { top->selected = i; if (i >= top->end) { @@ -1526,9 +1549,9 @@ static void loadLast(void) { top->start = top->end - ui.row_count; } } - if (arrlen(last) == 0 && !exactMatch(entry->path, FAUX_RECENT_PATH) && - !(!exactMatch(entry->path, COLLECTIONS_PATH) && - prefixMatch(COLLECTIONS_PATH, entry->path))) + if (arrlen(last) == 0 && !exactMatch(entry->path, g_faux_recent_path) && + !(!exactMatch(entry->path, g_collections_path) && + prefixMatch(g_collections_path, entry->path))) break; // don't show contents of auto-launch dirs if (entry->type == ENTRY_DIR) { @@ -1564,7 +1587,7 @@ static void Menu_init(void) { stack = NULL; // stb_ds dynamic array, starts as NULL recents = NULL; // stb_ds dynamic array, starts as NULL - openDirectory(SDCARD_PATH, 0); + openDirectory(g_sdcard_path, 0); loadLast(); // restore state when available } @@ -1674,13 +1697,16 @@ int main(int argc, char* argv[]) { // This must happen before any LOG_* calls to ensure crash-safe logging log_open(NULL); + // Initialize runtime paths from environment (supports LessOS dynamic storage) + Paths_init(); + // Check for auto-resume first (fast path) if (autoResume()) { log_close(); return 0; } - simple_mode = exists(SIMPLE_MODE_PATH); + simple_mode = exists(g_simple_mode_path); // Initialize context with pointers to globals // This enables navigation functions to be tested and migrated incrementally @@ -1713,7 +1739,7 @@ int main(int argc, char* argv[]) { LauncherThumbnail_loaderInit(); LOG_debug("EmuCache_init"); - int emu_count = EmuCache_init(PAKS_PATH, SDCARD_PATH, PLATFORM); + int emu_count = EmuCache_init(g_paks_path, g_sdcard_path, PLATFORM); LOG_info("Cached %d emulators", emu_count); LOG_debug("ResCache_init"); @@ -2135,7 +2161,10 @@ int main(int argc, char* argv[]) { if (show_version) { if (!version) { char release[256]; - getFile(ROOT_SYSTEM_PATH "/version.txt", release, 256); + char version_path[MAX_PATH]; + (void)snprintf(version_path, sizeof(version_path), "%s/version.txt", + g_root_system_path); + getFile(version_path, release, 256); char *tmp, *commit; commit = strrchr(release, '\n'); diff --git a/workspace/all/launcher/launcher_directory.c b/workspace/all/launcher/launcher_directory.c index ec6b3b51..f23ab57d 100644 --- a/workspace/all/launcher/launcher_directory.c +++ b/workspace/all/launcher/launcher_directory.c @@ -7,6 +7,7 @@ #include "launcher_directory.h" #include "launcher_file_utils.h" +#include "log.h" #include "utils.h" #include #include @@ -68,15 +69,18 @@ int LauncherDir_determineEntryType(const char* filename, int is_dir, const char* int LauncherDir_hasRoms(const char* dir_name, const char* roms_path, const char* paks_path, const char* sdcard_path, const char* platform) { if (!dir_name || !roms_path || !paks_path || !sdcard_path || !platform) { + LOG_debug("hasRoms: NULL param for '%s'", dir_name ? dir_name : "(null)"); return 0; } // Get emulator name from directory name char emu_name[256]; getEmuName(dir_name, emu_name); + LOG_debug("hasRoms: dir='%s' -> emu='%s'", dir_name, emu_name); // Check for emulator pak if (!Launcher_hasEmu(emu_name, paks_path, sdcard_path, platform)) { + LOG_debug("hasRoms: No emu pak for '%s'", emu_name); return 0; } @@ -84,7 +88,9 @@ int LauncherDir_hasRoms(const char* dir_name, const char* roms_path, const char* char rom_path[LAUNCHER_DIR_MAX_PATH]; (void)snprintf(rom_path, sizeof(rom_path), "%s/%s", roms_path, dir_name); - return Launcher_hasNonHiddenFiles(rom_path); + int has_files = Launcher_hasNonHiddenFiles(rom_path); + LOG_debug("hasRoms: path='%s' hasFiles=%d", rom_path, has_files); + return has_files; } /** diff --git a/workspace/all/launcher/launcher_file_utils.c b/workspace/all/launcher/launcher_file_utils.c index 25ed102b..a84ca4cd 100644 --- a/workspace/all/launcher/launcher_file_utils.c +++ b/workspace/all/launcher/launcher_file_utils.c @@ -6,6 +6,7 @@ #include "launcher_file_utils.h" #include "defines.h" +#include "log.h" #include "utils.h" #include #include @@ -30,13 +31,18 @@ int Launcher_hasEmu(char* emu_name, const char* paks_path, const char* sdcard_pa // Try shared location first (void)snprintf(pak_path, sizeof(pak_path), "%s/Emus/%s.pak/launch.sh", paks_path, emu_name); - if (exists(pak_path)) + if (exists(pak_path)) { + LOG_debug("hasEmu: found '%s'", pak_path); return 1; + } + LOG_debug("hasEmu: not found '%s'", pak_path); // Try platform-specific location (void)snprintf(pak_path, sizeof(pak_path), "%s/Emus/%s/%s.pak/launch.sh", sdcard_path, platform, emu_name); - return exists(pak_path); + int found = exists(pak_path); + LOG_debug("hasEmu: %s '%s'", found ? "found" : "not found", pak_path); + return found; } /** diff --git a/workspace/all/paks/LessUI/launch.sh.template b/workspace/all/paks/LessUI/launch.sh.template index 5794cb52..4f91324f 100644 --- a/workspace/all/paks/LessUI/launch.sh.template +++ b/workspace/all/paks/LessUI/launch.sh.template @@ -8,7 +8,8 @@ export PLATFORM="{{PLATFORM}}" export PLATFORM_ARCH="{{PLATFORM_ARCH}}" -export SDCARD_PATH="{{SDCARD_PATH}}" +# Use LESSOS_STORAGE if set (LessOS), otherwise use build-time default +export SDCARD_PATH="${LESSOS_STORAGE:-{{SDCARD_PATH}}}" export BIOS_PATH="$SDCARD_PATH/Bios" export SAVES_PATH="$SDCARD_PATH/Saves" export SYSTEM_PATH="$SDCARD_PATH/.system/$PLATFORM" @@ -44,9 +45,11 @@ export LD_LIBRARY_PATH="$SYSTEM_PATH/lib:$LD_LIBRARY_PATH" if [ -f "$SDCARD_PATH/.system/common/log.sh" ]; then . "$SDCARD_PATH/.system/common/log.sh" log_init "$LOGS_PATH/shell.log" + log_info "LessUI starting for $PLATFORM" fi {{HOOK:init}} +type log_info >/dev/null 2>&1 && log_info "Platform init complete" ####################################### # User auto.sh @@ -64,11 +67,16 @@ EXEC_PATH="/tmp/launcher_exec" NEXT_PATH="/tmp/next" touch "$EXEC_PATH" && sync +type log_info >/dev/null 2>&1 && log_info "Entering main loop" + while [ -f "$EXEC_PATH" ]; do cpu_restore 2>/dev/null || true type pre_launcher_hook >/dev/null 2>&1 && pre_launcher_hook + type log_info >/dev/null 2>&1 && log_info "Starting launcher.elf" LOG_FILE="$LOGS_PATH/launcher.log" launcher.elf + LAUNCHER_EXIT=$? + type log_info >/dev/null 2>&1 && log_info "launcher.elf exited with code: $LAUNCHER_EXIT" echo "$(date +'%F %T')" > "$DATETIME_PATH" sync diff --git a/workspace/all/paks/LessUI/platforms/rk3566/init.sh b/workspace/all/paks/LessUI/platforms/rk3566/init.sh new file mode 100644 index 00000000..2573b940 --- /dev/null +++ b/workspace/all/paks/LessUI/platforms/rk3566/init.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# RK3566 platform initialization (LessOS) +# Supports: RGB30, RG353P/M/V/VS, and other RK3566 devices + +type log_info >/dev/null 2>&1 && log_info "RK3566 platform init starting" + +# CPU setup +echo userspace >/sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null +CPU_PATH=/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed +CPU_SPEED_PERF=1992000 + +cpu_restore() { + echo "$CPU_SPEED_PERF" >"$CPU_PATH" 2>/dev/null +} +cpu_restore +type log_info >/dev/null 2>&1 && log_info "CPU governor set to userspace" + +# SDL environment +export SDL_AUDIODRIVER=alsa +type log_info >/dev/null 2>&1 && log_info "SDL_AUDIODRIVER=$SDL_AUDIODRIVER" + +# Start keymon +type log_info >/dev/null 2>&1 && log_info "Starting keymon..." +LOG_FILE="$LOGS_PATH/keymon.log" keymon.elf & +KEYMON_PID=$! +type log_info >/dev/null 2>&1 && log_info "keymon started (PID: $KEYMON_PID)" diff --git a/workspace/all/paks/Tools/Clock/src/clock.c b/workspace/all/paks/Tools/Clock/src/clock.c index b6a39246..8d200f84 100644 --- a/workspace/all/paks/Tools/Clock/src/clock.c +++ b/workspace/all/paks/Tools/Clock/src/clock.c @@ -23,6 +23,7 @@ #include "api.h" #include "defines.h" +#include "paths.h" #include "utils.h" /** @@ -49,6 +50,7 @@ enum { * @return EXIT_SUCCESS on normal exit */ int main(int argc, char* argv[]) { + Paths_init(); PWR_setCPUSpeed(CPU_SPEED_IDLE); SDL_Surface* screen = GFX_init(MODE_MAIN); @@ -90,7 +92,9 @@ int main(int argc, char* argv[]) { int save_changes = 0; int select_cursor = 0; // Check if user prefers 24-hour format (stored as a flag file) - int show_24hour = exists(USERDATA_PATH "/show_24hour"); + char pref_path[MAX_PATH]; + (void)snprintf(pref_path, sizeof(pref_path), "%s/show_24hour", g_userdata_path); + int show_24hour = exists(pref_path); // Initialize with current system time time_t t = time(NULL); @@ -268,9 +272,9 @@ int main(int argc, char* argv[]) { // Persist preference as a flag file if (show_24hour) { - system("touch " USERDATA_PATH "/show_24hour"); + touch(pref_path); } else { - system("rm " USERDATA_PATH "/show_24hour"); + unlink(pref_path); } } diff --git a/workspace/all/paks/Tools/System Report/pak.json b/workspace/all/paks/Tools/System Report/pak.json index 8667f0e3..67d724ec 100644 --- a/workspace/all/paks/Tools/System Report/pak.json +++ b/workspace/all/paks/Tools/System Report/pak.json @@ -13,6 +13,7 @@ "tg5040", "zero28", "rgb30", + "rk3566", "m17", "my282", "magicmini" diff --git a/workspace/all/paks/config/platforms.json b/workspace/all/paks/config/platforms.json index be6daf76..cad1c7d6 100644 --- a/workspace/all/paks/config/platforms.json +++ b/workspace/all/paks/config/platforms.json @@ -56,6 +56,14 @@ "shutdown_cmd": "shutdown", "nice_prefix": "" }, + "rk3566": { + "description": "Rockchip RK3566 devices (LessOS)", + "arch": "arm64", + "sdcard_path": "/storage", + "shutdown_cmd": "poweroff", + "nice_prefix": "", + "lessos": true + }, "m17": { "description": "Powkiddy X55", "arch": "arm32", diff --git a/workspace/all/player/Makefile b/workspace/all/player/Makefile index 666c870b..3c51aedd 100644 --- a/workspace/all/player/Makefile +++ b/workspace/all/player/Makefile @@ -25,6 +25,7 @@ INCDIR = -I. -I./libretro-common/include/ -I../common/ -I../launcher/ -isystem . SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/nointro_parser.c \ ../common/api.c ../common/ui_layout.c ../common/log.c ../common/pad.c \ ../common/gfx_text.c ../launcher/launcher_file_utils.c ../common/platform_variant.c \ + ../common/paths.c \ player_archive.c player_memory.c player_state.c \ player_paths.c player_cpu.c player_input.c player_mappings.c \ player_video_convert.c player_rotation.c player_config.c player_context.c \ diff --git a/workspace/all/player/player.c b/workspace/all/player/player.c index a93bb1e6..09c463af 100644 --- a/workspace/all/player/player.c +++ b/workspace/all/player/player.c @@ -60,6 +60,7 @@ #include "gl_video.h" #include "launcher_file_utils.h" #include "libretro.h" +#include "paths.h" #include "player_archive.h" #include "player_config.h" #include "player_context.h" @@ -1693,12 +1694,13 @@ static void Config_load(void) { player_scaling_labels[3] = NULL; } - char* system_path = SYSTEM_PATH "/system.cfg"; + char system_path[MAX_PATH]; + (void)snprintf(system_path, sizeof(system_path), "%s/system.cfg", g_system_path); char device_system_path[MAX_PATH] = {0}; if (config.device_tag) - (void)snprintf(device_system_path, sizeof(device_system_path), SYSTEM_PATH "/system-%s.cfg", - config.device_tag); + (void)snprintf(device_system_path, sizeof(device_system_path), "%s/system-%s.cfg", + g_system_path, config.device_tag); if (config.device_tag && exists(device_system_path)) { LOG_debug("Using device_system_path: %s", device_system_path); @@ -3983,11 +3985,14 @@ void Core_getName(char* in_name, char* out_name) { * @param bios_dir Output buffer for selected BIOS directory path */ void Player_selectBiosPath(const char* tag, char* bios_dir) { + char bios_root[MAX_PATH]; + (void)snprintf(bios_root, sizeof(bios_root), "%s/Bios", g_sdcard_path); + char tag_bios_dir[MAX_PATH]; - PlayerPaths_getTagBios(SDCARD_PATH "/Bios", tag, tag_bios_dir); + PlayerPaths_getTagBios(bios_root, tag, tag_bios_dir); int has_files = Launcher_hasNonHiddenFiles(tag_bios_dir); - PlayerPaths_chooseBios(SDCARD_PATH "/Bios", tag, bios_dir, has_files); + PlayerPaths_chooseBios(bios_root, tag, bios_dir, has_files); if (has_files) { LOG_info("Using tag-specific BIOS directory: %s", bios_dir); @@ -4067,11 +4072,11 @@ void Core_open(const char* core_path, const char* tag_name) { LOG_info("core: %s version: %s tag: %s (valid_extensions: %s need_fullpath: %i)", core.name, core.version, core.tag, info.valid_extensions, info.need_fullpath); - (void)snprintf((char*)core.config_dir, sizeof(core.config_dir), USERDATA_PATH "/%s-%s", - core.tag, core.name); - (void)snprintf((char*)core.states_dir, sizeof(core.states_dir), SHARED_USERDATA_PATH "/%s-%s", + (void)snprintf((char*)core.config_dir, sizeof(core.config_dir), "%s/%s-%s", g_userdata_path, core.tag, core.name); - (void)snprintf((char*)core.saves_dir, sizeof(core.saves_dir), SDCARD_PATH "/Saves/%s", + (void)snprintf((char*)core.states_dir, sizeof(core.states_dir), "%s/%s-%s", + g_shared_userdata_path, core.tag, core.name); + (void)snprintf((char*)core.saves_dir, sizeof(core.saves_dir), "%s/Saves/%s", g_sdcard_path, core.tag); Player_selectBiosPath(core.tag, (char*)core.bios_dir); @@ -5648,6 +5653,9 @@ int main(int argc, char* argv[]) { // Initialize logging early (reads LOG_FILE and LOG_SYNC from environment) log_open(NULL); + // Initialize runtime paths (reads LESSOS_STORAGE from environment) + Paths_init(); + LOG_info("Player"); // Initialize context with pointers to globals diff --git a/workspace/all/player/player_menu.c b/workspace/all/player/player_menu.c index 1debe0be..7ed02001 100644 --- a/workspace/all/player/player_menu.c +++ b/workspace/all/player/player_menu.c @@ -20,6 +20,7 @@ #include "defines.h" #include "gl_video.h" #include "log.h" +#include "paths.h" #include "player_context.h" #include "player_internal.h" #include "player_mappings.h" @@ -113,8 +114,8 @@ static void Menu_init_ctx(PlayerContext* ctx) { char emu_name[256]; getEmuName(g->path, emu_name); - (void)snprintf(m->launcher_dir, sizeof(m->launcher_dir), SHARED_USERDATA_PATH "/.launcher/%s", - emu_name); + (void)snprintf(m->launcher_dir, sizeof(m->launcher_dir), "%s/.launcher/%s", + g_shared_userdata_path, emu_name); mkdir(m->launcher_dir, 0755); (void)snprintf(m->slot_path, sizeof(m->slot_path), "%s/%s.txt", m->launcher_dir, g->name); @@ -185,12 +186,12 @@ static void Menu_beforeSleep_ctx(PlayerContext* ctx) { cb->rtc_write(); cb->state_autosave(); sync(); - putFile(AUTO_RESUME_PATH, g->path + strlen(SDCARD_PATH)); + putFile(g_auto_resume_path, g->path + strlen(g_sdcard_path)); PWR_setCPUSpeed(CPU_SPEED_IDLE); } static void Menu_afterSleep_ctx(PlayerContext* ctx) { - unlink(AUTO_RESUME_PATH); + unlink(g_auto_resume_path); ctx->callbacks->set_overclock(*ctx->overclock); } diff --git a/workspace/all/utils/shui/fonts.c b/workspace/all/utils/shui/fonts.c index ec48837f..577396be 100644 --- a/workspace/all/utils/shui/fonts.c +++ b/workspace/all/utils/shui/fonts.c @@ -2,6 +2,7 @@ #include "fonts.h" #include "defines.h" +#include "paths.h" TTF_Font* g_font_large = NULL; TTF_Font* g_font_small = NULL; @@ -9,12 +10,12 @@ TTF_Font* g_font_small = NULL; void fonts_init(void) { if (g_font_large) return; - g_font_large = TTF_OpenFont(FONT_PATH, DP(FONT_LARGE)); + g_font_large = TTF_OpenFont(g_font_path, DP(FONT_LARGE)); if (g_font_large) { TTF_SetFontStyle(g_font_large, TTF_STYLE_BOLD); } - g_font_small = TTF_OpenFont(FONT_PATH, DP(FONT_SMALL)); + g_font_small = TTF_OpenFont(g_font_path, DP(FONT_SMALL)); } void fonts_cleanup(void) { diff --git a/workspace/all/utils/shui/shui.c b/workspace/all/utils/shui/shui.c index de9a38ed..2520bb5e 100644 --- a/workspace/all/utils/shui/shui.c +++ b/workspace/all/utils/shui/shui.c @@ -34,6 +34,7 @@ #include "defines.h" #include "fonts.h" #include "log.h" +#include "paths.h" #include "shui_utils.h" #include "ui_message.h" #include "ui_list.h" @@ -845,6 +846,9 @@ static int run_daemon(void) { // Initialize logging early (reads LOG_FILE and LOG_SYNC from environment) log_open(NULL); + // Initialize runtime paths (reads LESSOS_STORAGE from environment) + Paths_init(); + LOG_info("Starting shui daemon on %s", PLATFORM); // Setup signal handling diff --git a/workspace/rk3566/Makefile b/workspace/rk3566/Makefile new file mode 100644 index 00000000..9274d056 --- /dev/null +++ b/workspace/rk3566/Makefile @@ -0,0 +1,28 @@ +########################################################### + +ifeq (,$(PLATFORM)) +PLATFORM = $(UNION_PLATFORM) +endif + +ifeq (,$(PLATFORM)) +$(error please specify PLATFORM, eg. PLATFORM=rk3566 make) +endif + +########################################################### + +# LessOS provides SDL2 natively - no sdl12-compat needed + +# keymon.elf is now built in workspace/all/utils +# LessOS doesn't need DinguxCommander (file manager for stock firmware) +all: readmes show + +.PHONY: show +show: + @$(MAKE) -C show + +# No early dependencies for rk3566 +early: + +clean: + +include ../all/readmes/Makefile \ No newline at end of file diff --git a/workspace/rk3566/README.md b/workspace/rk3566/README.md new file mode 100644 index 00000000..fbadd354 --- /dev/null +++ b/workspace/rk3566/README.md @@ -0,0 +1,330 @@ +# Rockchip RK3566 Platform + +Platform implementation for Rockchip RK3566-based retro handheld devices (LessOS). + +## Supported Devices +- PowKiddy RGB30 +- Anbernic RG353P/M/V/VS (future) + +## Hardware Specifications + +### Display +- **Resolution**: 720x720 (square display, 1:1 aspect ratio) +- **Color Depth**: 16-bit RGB565 +- **UI Scale**: 2x (uses `assets@2x.png`) +- **HDMI Output**: 1280x720 (720p) with automatic switching + +### Input +- **D-Pad**: Up, Down, Left, Right +- **Face Buttons**: A, B, X, Y +- **Shoulder Buttons**: L1, R1, L2, R2 +- **Analog Sticks**: L3, R3 (clickable, used as menu modifiers) +- **System Buttons**: + - Two MENU buttons (primary and alternate via L3/R3) + - POWER button + - Dedicated PLUS/MINUS volume buttons + - SELECT and START buttons + +### Input Method +- **Primary**: Evdev input events (direct reading from `/dev/input/event*`) +- **Implementation**: Reads raw `input_event` structures for all input +- **Notable**: Bypasses SDL input APIs entirely + +### CPU & Performance +- ARM processor with NEON SIMD support +- No overclocking support + +### Power Management +- Power button detection via SDL keyboard (SDLK_POWER) +- Sleep/wake functionality + +### Audio/Video +- **Headphone Jack**: Automatic detection and audio routing +- **HDMI Output**: Automatic detection with video/audio switching +- **Volume Range**: 0-20 (raw: 0-100 via amixer) +- **Brightness Range**: 0-10 (raw: 0-255) + +### Storage +- SD card mounted at `/storage` (LessOS) + +## Platform Architecture + +RK3566 devices use a **hybrid input system** that combines: +- SDL2 joystick for gamepad buttons (D-Pad, face buttons, shoulders) +- Minimal SDL keyboard for power button +- Evdev codes for volume control and L3/R3 menu modifiers + +This approach allows hardware volume buttons to work independently while maintaining standard gamepad compatibility. + +## Directory Structure + +``` +RK3566/ +├── platform/ Platform-specific hardware definitions +│ └── platform.h Button mappings, display specs, HDMI support +├── keymon/ Hardware button monitoring daemon +│ └── keymon.c Volume/brightness + headphone/HDMI detection +├── libmsettings/ Settings library with audio/video routing +│ ├── msettings.c Shared memory settings management +│ └── msettings.h Settings API (volume, brightness, jack, HDMI) +├── show/ Boot splash screen utility +│ ├── show.c SDL2-based image display (supports HDMI) +│ └── makefile Build configuration +└── cores/ Libretro cores (submodules + builds) +``` + +## Input System + +RK3566 devices use a **joystick-first input architecture**: + +1. **SDL Joystick API**: Primary gamepad input (D-Pad, face buttons, shoulders, Start/Select) +2. **SDL Keyboard**: Power button only (SDLK_POWER) +3. **Evdev Codes**: Volume buttons (114/115) and L3/R3 menu modifiers (317/318) + +### Joystick Button Mappings + +| Physical Button | Joystick Index | +|----------------|----------------| +| D-Pad Up/Down/Left/Right | 13/14/15/16 | +| A, B, X, Y | 1, 0, 2, 3 | +| L1, R1 | 4, 5 | +| L2, R2 | 6, 7 | +| SELECT, START | 8, 9 | +| MENU (primary) | 11 | +| MENU ALT (secondary) | 12 | + +### Input Event Devices + +The keymon daemon monitors multiple input devices: +- **event0-4**: Gamepad and button input +- **js0**: Joystick device + +### Button Combinations + +| Combination | Function | +|-------------|----------| +| PLUS | Increase volume | +| MINUS | Decrease volume | +| L3/R3 + PLUS | Increase brightness | +| L3/R3 + MINUS | Decrease brightness | +| POWER | Sleep/wake device | +| X (in launcher) | Resume from save state | + +### Hardware Monitoring + +The keymon daemon runs a background thread to monitor: +- **Headphone jack state**: `/sys/bus/platform/devices/singleadc-joypad/hp` +- **HDMI connection state**: `/sys/class/extcon/hdmi/cable.0/state` + +Audio and video routing automatically switches when headphones or HDMI are connected/disconnected. + +## Building + +### Prerequisites +Requires Docker with RK3566 cross-compilation toolchain. + +### Build Commands + +```bash +# Enter platform build environment +make PLATFORM=rk3566 shell + +# Inside container: build all platform components +cd /root/workspace/rk3566 +make + +# This builds: +# - show.elf (boot splash display) +``` + +## Installation + +### File System Layout + +LessUI installs to the SD card with the following structure: + +``` +/storage/ +├── lessos/ LessOS bootstrap +│ └── init.sh Entry point +├── .system/ +│ ├── RK3566/ Platform-specific binaries +│ │ ├── bin/ Utilities (keymon, show, etc.) +│ │ └── paks/ Applications and emulators +│ │ └── LessUI.pak/ Main launcher +│ └── res/ Shared UI assets +│ ├── assets@2x.png UI sprite sheet (2x scale) +│ └── InterTight-Bold.ttf +├── .userdata/ User settings and save data +│ └── RK3566/ Platform-specific settings +│ └── msettings.bin Volume/brightness preferences +├── Roms/ ROM files organized by system +└── LessUI.7z Update package (if present) +``` + +### Settings Management + +RK3566 devices use shared memory for settings synchronization between processes: + +**Settings Stored**: +- Brightness level (0-10) +- Speaker volume (0-20) +- Headphone volume (0-20, separate from speaker) +- Jack state (headphones connected/disconnected) +- HDMI state (HDMI connected/disconnected) + +**Persistence**: Settings are saved to `$USERDATA_PATH/msettings.bin` and persist across reboots. + +## Platform-Specific Features + +### Square Display + +The 720x720 square display (1:1 aspect ratio) requires UI adjustments: + +| Parameter | Value | Notes | +|-----------|-------|-------| +| `MAIN_ROW_COUNT` | 8 rows | More rows visible than standard 640x480 | +| `PADDING` | 40px | Larger padding for square layout | + +These settings maximize content visibility while maintaining comfortable spacing. + +### HDMI Output Support + +The platform includes full HDMI support: + +**Output Resolution**: 1280x720 (720p) + +**Automatic Behavior**: +- Video output switches to HDMI when connected +- Audio routing changes to HDMI audio +- Volume locked at 100% on HDMI (brightness control disabled) +- Automatically reverts to internal display/speaker when disconnected + +**Implementation**: Background thread in keymon polls HDMI state every second. + +### Audio Routing + +Smart audio routing based on connected devices: + +1. **Internal Speaker**: Default audio output +2. **Headphones**: Auto-detected, switches to headphone output, uses separate volume level +3. **HDMI**: Auto-detected, switches to HDMI audio, locks volume at 100% + +**amixer Commands**: +- Playback path: `amixer cset name='Playback Path' 'SPK'|'HP'` +- Volume: `amixer sset -M 'Master' N%` + +### Brightness Control + +Hardware brightness via sysfs: +- **Path**: `/sys/class/backlight/backlight/brightness` +- **Range**: 0-255 (raw), mapped from 0-10 user scale +- **Disabled on HDMI**: Brightness adjustments ignored when HDMI connected + +**Brightness Curve** (non-linear for better low-end visibility): +``` +Level 0: 4 Level 1: 6 Level 2: 10 Level 3: 16 +Level 4: 32 Level 5: 48 Level 6: 64 Level 7: 96 +Level 8: 128 Level 9: 192 Level 10: 255 +``` + +### Volume Button Quirks + +The platform has hardware volume buttons with evdev codes: +- **CODE_PLUS**: 129 (Volume up) - **swapped from kernel default** +- **CODE_MINUS**: 128 (Volume down) - **swapped from kernel default** + +These codes are swapped in platform.h to correct the hardware mapping. + +### Repeat Functionality + +Volume and brightness buttons support repeat: +- **Initial delay**: 300ms before repeat starts +- **Repeat interval**: 100ms between repeats +- **Ignores stale input**: After system sleep (>1 second gap), input ignored to prevent spurious events + +## Supported Emulator Cores + +The RK3566 platform includes these libretro cores: + +| Core | Systems | Notes | +|------|---------|-------| +| **fceumm** | NES, Famicom | Nintendo Entertainment System | +| **gambatte** | Game Boy, Game Boy Color | Accuracy-focused | +| **gpsp** | Game Boy Advance | Fast, requires BIOS | +| **pcsx_rearmed** | PlayStation | Optimized ARM port | +| **picodrive** | Genesis, Mega Drive, 32X, Sega CD | Multi-system | +| **snes9x2005_plus** | SNES | Lightweight, Blargg APU | +| **pokemini** | Pokemon Mini | Niche handheld | +| **fake-08** | PICO-8 | Fantasy console | +| **mednafen_pce_fast** | PC Engine, TurboGrafx-16 | Fast variant | +| **mednafen_vb** | Virtual Boy | Nintendo VR handheld | +| **mednafen_supafaust** | SNES | High accuracy | +| **mgba** | Game Boy Advance | High accuracy | +| **race** | Neo Geo Pocket, Neo Geo Pocket Color | SNK handheld | + +## Known Issues / Quirks + +### Hardware Quirks +1. **Joystick-first input**: Unlike keyboard-based platforms, uses SDL joystick API primarily +2. **Dual menu buttons**: L3/R3 analog stick clicks act as menu modifiers (codes 317/318) +3. **Volume code swap**: Hardware volume button codes swapped in software to correct mapping +4. **HDMI volume lock**: Volume/brightness controls disabled when HDMI connected +5. **Audio crackling**: Light crackling reported in some cores (fceumm, snes9x2005_plus) - likely SDL2 audio pipeline issue + +### Development Notes +1. **SDL2**: Platform uses SDL2 natively (LessOS provides SDL2) +2. **Shared memory settings**: libmsettings uses `/SharedSettings` SHM for IPC +3. **Settings host**: keymon is always the settings "host" (first to create SHM) +4. **HDMI polling**: Background thread polls HDMI state at 1Hz (every second) +5. **60Hz input polling**: Main keymon loop runs at 60Hz (16.6ms sleep) + +### Input Limitations +- **No L3/R3 for games**: L3/R3 reserved for system menu modifier, not available to cores +- **Evdev overhead**: Volume buttons require evdev monitoring (can't use SDL alone) + +### Audio Buffer Size +Default audio buffer: `SAMPLES 400` (balanced latency vs stability) + +## Testing + +When testing changes: +1. Verify joystick input works correctly (D-Pad, face buttons, shoulders) +2. Test HDMI detection and automatic video/audio switching +3. Check headphone jack detection and audio routing +4. Verify volume control with PLUS/MINUS buttons +5. Test brightness control with L3/R3 + PLUS/MINUS +6. Confirm settings persist across reboots (msettings.bin) +7. Test on both internal display and HDMI output +8. Verify square display UI layout (720x720) + +## Related Documentation + +- Main project docs: `../../README.md` +- Platform abstraction: `../../all/common/defines.h` +- Shared code: `../../all/launcher/launcher.c` (launcher), `../../all/player/player.c` (libretro frontend) +- Build system: `../../Makefile` (host), `./makefile` (platform) +- Platform header: `./platform/platform.h` (all hardware definitions) + +## Maintainer Notes + +This platform demonstrates several advanced LessUI features: + +**Unique Characteristics**: +- Square display (720x720) - unique aspect ratio among LessUI platforms +- HDMI output support with automatic switching +- Dual menu modifier buttons (L3/R3) +- Hybrid input system (joystick + keyboard + evdev) +- Background hardware monitoring threads +- Shared memory settings management +- Separate volume levels for speaker vs headphones + +**Reference Implementation For**: +- HDMI output detection and switching +- Headphone jack detection +- Multi-device audio routing +- Non-standard display aspect ratios +- SDL2-based platforms +- Joystick-first input handling + +Changes to this platform should be tested with HDMI output and headphone jack to ensure automatic switching continues to work correctly. diff --git a/workspace/rk3566/install/boot.sh b/workspace/rk3566/install/boot.sh new file mode 100755 index 00000000..15adc2b8 --- /dev/null +++ b/workspace/rk3566/install/boot.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# NOTE: becomes .tmp_update/rk3566.sh + +PLATFORM="rk3566" +# Use LessOS storage path if set, otherwise default +SDCARD_PATH="${LESSOS_STORAGE:-/storage}" +UPDATE_PATH="$SDCARD_PATH/LessUI.7z" +SYSTEM_PATH="$SDCARD_PATH/.system" +LOG_FILE="$SDCARD_PATH/lessui-install.log" +BOOT_LOG="$SDCARD_PATH/lessui-boot.log" + +# Source shared update functions +. "$(dirname "$0")/install/update-functions.sh" + +# Boot logging helper +boot_log() { + echo "[$(date '+%H:%M:%S')] $*" >>"$BOOT_LOG" +} +echo "=== LessUI boot started: $(date) ===" >"$BOOT_LOG" +boot_log "PLATFORM=$PLATFORM" +boot_log "SDCARD_PATH=$SDCARD_PATH" +boot_log "LESSOS_PLATFORM=$LESSOS_PLATFORM" +boot_log "LESSOS_STORAGE=$LESSOS_STORAGE" + +# CPU setup +echo userspace >/sys/devices/system/cpu/cpufreq/policy0/scaling_governor +CPU_PATH=/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed +CPU_SPEED_PERF=1992000 +echo $CPU_SPEED_PERF >$CPU_PATH +boot_log "CPU governor set" + +# install/update +if [ -f "$UPDATE_PATH" ]; then + cd $(dirname "$0")/$PLATFORM + boot_log "Changed to $(pwd)" + + if [ -d "$SYSTEM_PATH" ]; then + ACTION="update" + boot_log "Running: ./show.elf ./updating.png" + ./show.elf ./updating.png 2>>"$BOOT_LOG" + boot_log "show.elf exit code: $?" + else + ACTION="installation" + boot_log "Running: ./show.elf ./installing.png" + ./show.elf ./installing.png 2>>"$BOOT_LOG" + boot_log "show.elf exit code: $?" + fi + + log_info "Starting LessUI $ACTION..." + + # Perform atomic update with automatic rollback + atomic_system_update "$UPDATE_PATH" "$SDCARD_PATH" "$SYSTEM_PATH" "$LOG_FILE" + boot_log "atomic_system_update completed: $?" + + # Run platform-specific install script + run_platform_install "$SYSTEM_PATH/$PLATFORM/bin/install.sh" "$LOG_FILE" + boot_log "run_platform_install completed: $?" + + # Return to valid directory (atomic_system_update may have deleted .tmp_update) + cd "$SDCARD_PATH" + boot_log "Returned to $SDCARD_PATH" +fi + +LAUNCH_PATH="$SYSTEM_PATH/$PLATFORM/paks/LessUI.pak/launch.sh" +boot_log "LAUNCH_PATH=$LAUNCH_PATH" +boot_log "File exists: $([ -f "$LAUNCH_PATH" ] && echo yes || echo no)" + +while [ -f "$LAUNCH_PATH" ]; do + boot_log "Starting launch.sh..." + "$LAUNCH_PATH" 2>>"$BOOT_LOG" + LAUNCH_EXIT=$? + boot_log "launch.sh exited with code: $LAUNCH_EXIT" +done + +boot_log "Exiting boot loop, rebooting..." +reboot diff --git a/workspace/rk3566/libmsettings/Makefile b/workspace/rk3566/libmsettings/Makefile new file mode 100644 index 00000000..bc879d7b --- /dev/null +++ b/workspace/rk3566/libmsettings/Makefile @@ -0,0 +1,32 @@ +ifeq (,$(CROSS_COMPILE)) +$(error missing CROSS_COMPILE for this toolchain) +endif +ifeq (,$(PREFIX)) +$(error missing PREFIX for this toolchain) +endif + +TARGET = msettings + +.PHONY: build +.PHONY: clean + +CC = $(CROSS_COMPILE)gcc + +SYSROOT := $(shell $(CC) --print-sysroot) + +INCLUDEDIR = $(SYSROOT)/usr/include +# OPT_FLAGS from parent makefile (-O3 for release, -O0 -g for debug) +OPT_FLAGS ?= -O3 +CFLAGS = -I$(INCLUDEDIR) $(OPT_FLAGS) +LDFLAGS = -ldl -lrt -s + +build: + @$(CC) -c -Werror -fpic "$(TARGET).c" -Wl,--no-as-needed $(CFLAGS) + @$(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" $(LDFLAGS) + @cp "$(TARGET).h" "$(PREFIX)/include" + @cp "lib$(TARGET).so" "$(PREFIX)/lib" +clean: + rm -f *.o + rm -f "lib$(TARGET).so" + rm -f $(PREFIX)/include/$(TARGET).h + rm -f $(PREFIX)/lib/lib$(TARGET).so \ No newline at end of file diff --git a/workspace/rk3566/libmsettings/msettings.c b/workspace/rk3566/libmsettings/msettings.c new file mode 100644 index 00000000..ab87f4b1 --- /dev/null +++ b/workspace/rk3566/libmsettings/msettings.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msettings.h" + +/////////////////////////////////////// + +#define SETTINGS_VERSION 2 +typedef struct Settings { + int version; // future proofing + int brightness; + int headphones; + int speaker; + int unused[2]; // for future use + // NOTE: doesn't really need to be persisted but still needs to be shared + int jack; + int hdmi; +} Settings; +static Settings DefaultSettings = { + .version = SETTINGS_VERSION, + .brightness = 2, + .headphones = 4, + .speaker = 8, + .jack = 0, + .hdmi = 0, +}; +static Settings* settings; + +#define SHM_KEY "/SharedSettings" +static char SettingsPath[256]; +static int shm_fd = -1; +static int is_host = 0; +static int shm_size = sizeof(Settings); + +#define BACKLIGHT_PATH "/sys/class/backlight/backlight/bl_power" +#define BRIGHTNESS_PATH "/sys/class/backlight/backlight/brightness" +#define JACK_STATE_PATH "/sys/bus/platform/devices/singleadc-joypad/hp" +#define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +int getInt(char* path) { + int i = 0; + FILE *file = fopen(path, "r"); + if (file!=NULL) { + fscanf(file, "%i", &i); + fclose(file); + } + return i; +} + + +void InitSettings(void) { + sprintf(SettingsPath, "%s/msettings.bin", getenv("USERDATA_PATH")); + + shm_fd = shm_open(SHM_KEY, O_RDWR | O_CREAT | O_EXCL, 0644); // see if it exists + if (shm_fd==-1 && errno==EEXIST) { // already exists + puts("Settings client"); + shm_fd = shm_open(SHM_KEY, O_RDWR, 0644); + settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + } + else { // host + puts("Settings host"); // should always be keymon + is_host = 1; + // we created it so set initial size and populate + ftruncate(shm_fd, shm_size); + settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + + int fd = open(SettingsPath, O_RDONLY); + if (fd>=0) { + read(fd, settings, shm_size); + // TODO: use settings->version for future proofing? + close(fd); + } + else { + // load defaults + memcpy(settings, &DefaultSettings, shm_size); + } + + // these shouldn't be persisted + // settings->jack = 0; + // settings->hdmi = 0; + } + + int jack = getInt(JACK_STATE_PATH); + int hdmi = getInt(HDMI_STATE_PATH); + printf("brightness: %i (hdmi: %i)\nspeaker: %i (jack: %i)\n", settings->brightness, hdmi, settings->speaker, jack); fflush(stdout); + + // both of these set volume + SetJack(jack); + SetHDMI(hdmi); + + SetBrightness(GetBrightness()); + // system("echo $(< " BRIGHTNESS_PATH ")"); +} +void QuitSettings(void) { + munmap(settings, shm_size); + if (is_host) shm_unlink(SHM_KEY); +} +static inline void SaveSettings(void) { + int fd = open(SettingsPath, O_CREAT|O_WRONLY, 0644); + if (fd>=0) { + write(fd, settings, shm_size); + close(fd); + sync(); + } +} + +int GetBrightness(void) { // 0-10 + return settings->brightness; +} +void SetBrightness(int value) { + if (settings->hdmi) return; + + int raw; + switch (value) { + // TODO: redo, range is 0-255 + case 0: raw=4; break; // 0 + case 1: raw=6; break; // 2 + case 2: raw=10; break; // 4 + case 3: raw=16; break; // 6 + case 4: raw=32; break; // 16 + case 5: raw=48; break; // 16 + case 6: raw=64; break; // 16 + case 7: raw=96; break; // 32 + case 8: raw=128; break; // 32 + case 9: raw=192; break; // 64 + case 10: raw=255; break; // 64 + } + SetRawBrightness(raw); + settings->brightness = value; + SaveSettings(); +} + +int GetVolume(void) { // 0-20 + return settings->jack ? settings->headphones : settings->speaker; +} +void SetVolume(int value) { + if (settings->hdmi) return; + + if (settings->jack) settings->headphones = value; + else settings->speaker = value; + + int raw = value * 5; + SetRawVolume(raw); + SaveSettings(); +} + +void SetRawBrightness(int val) { // 0 - 255 + if (settings->hdmi) return; + + // printf("SetRawBrightness(%i)\n", val); fflush(stdout); + int fd = open(BRIGHTNESS_PATH, O_WRONLY); + if (fd>=0) { + dprintf(fd,"%d",val); + close(fd); + } +} +void SetRawVolume(int val) { // 0 - 100 + // printf("SetRawVolume(%i)\n", val); fflush(stdout); + char cmd[256]; + sprintf(cmd, "amixer sset -M 'Master' %i%% &> /dev/null", val); + // puts(cmd); fflush(stdout); + system(cmd); +} + +// monitored and set by thread in keymon +int GetJack(void) { + return settings->jack; +} +void SetJack(int value) { + // printf("SetJack(%i)\n", value); fflush(stdout); + + char cmd[256]; + sprintf(cmd, "amixer cset name='Playback Path' '%s' &> /dev/null", value?"HP":"SPK"); + system(cmd); + + settings->jack = value; + SetVolume(GetVolume()); +} + +int GetHDMI(void) { + // printf("GetHDMI() %i\n", settings->hdmi); fflush(stdout); + return settings->hdmi; +} +void SetHDMI(int value) { + // printf("SetHDMI(%i)\n", value); fflush(stdout); + + // if (settings->hdmi!=value) system("/usr/lib/autostart/common/055-hdmi-check"); + + settings->hdmi = value; + if (value) SetRawVolume(100); // max + else SetVolume(GetVolume()); // restore +} + +int GetMute(void) { return 0; } +void SetMute(int value) { } \ No newline at end of file diff --git a/workspace/rk3566/libmsettings/msettings.h b/workspace/rk3566/libmsettings/msettings.h new file mode 100644 index 00000000..47e5fb00 --- /dev/null +++ b/workspace/rk3566/libmsettings/msettings.h @@ -0,0 +1,25 @@ +#ifndef __msettings_h__ +#define __msettings_h__ + +void InitSettings(void); +void QuitSettings(void); + +int GetBrightness(void); +int GetVolume(void); + +void SetRawBrightness(int value); // 0-1024 +void SetRawVolume(int value); // 0-40 + +void SetBrightness(int value); // 0-10 +void SetVolume(int value); // 0-20 + +int GetJack(void); +void SetJack(int value); // 0-1 + +int GetHDMI(void); +void SetHDMI(int value); // 0-1 + +int GetMute(void); +void SetMute(int value); // 0-1 + +#endif // __msettings_h__ diff --git a/workspace/rk3566/other/.keep b/workspace/rk3566/other/.keep new file mode 100644 index 00000000..e69de29b diff --git a/workspace/rk3566/platform/Makefile.copy b/workspace/rk3566/platform/Makefile.copy new file mode 100644 index 00000000..76da13e3 --- /dev/null +++ b/workspace/rk3566/platform/Makefile.copy @@ -0,0 +1,13 @@ +$(PLATFORM): + # $@ + cp ./workspace/$@/keymon/keymon.elf ./build/SYSTEM/$@/bin/ + # show.elf (platform-specific build) + cp ./workspace/$@/show/show.elf ./build/SYSTEM/$@/bin/ + # LessOS provides SDL2 natively - no sdl12-compat needed + # installer + rsync -a ./workspace/$@/install/boot.sh ./build/BOOT/common/$@.sh + mkdir -p ./build/BOOT/common/$@/ + cp ./workspace/$@/show/show.elf ./build/BOOT/common/$@/ + # boot assets + rsync -a ./skeleton/SYSTEM/res/installing@3x.png ./build/BOOT/common/$@/installing.png + rsync -a ./skeleton/SYSTEM/res/updating@3x.png ./build/BOOT/common/$@/updating.png \ No newline at end of file diff --git a/workspace/rk3566/platform/Makefile.env b/workspace/rk3566/platform/Makefile.env new file mode 100644 index 00000000..32bd0932 --- /dev/null +++ b/workspace/rk3566/platform/Makefile.env @@ -0,0 +1,4 @@ +# rk3566 - LessOS toolchain +ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 +LIBS = -flto -lrga -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 +SDL = SDL2 \ No newline at end of file diff --git a/workspace/rk3566/platform/platform.c b/workspace/rk3566/platform/platform.c new file mode 100644 index 00000000..435b454f --- /dev/null +++ b/workspace/rk3566/platform/platform.c @@ -0,0 +1,406 @@ +/** + * platform.c - Rockchip RK3566 platform implementation + * + * REFACTORED VERSION - Uses shared render_sdl2 backend + * + * Platform-specific code for Rockchip RK3566-based handheld devices. + * Supported devices: PowKiddy RGB30, Anbernic RG353P/M/V/VS + * + * Key features: + * - Dual analog sticks with swapped right stick axes (X/Y reversed) + * - WiFi support with status monitoring + * - Grid and line visual effects for retro aesthetics + * - Rotation support for display output + * - Dynamic device model detection from device tree + * - Overscan support (PLAT_supportsOverscan returns 1) + * + * RK3566 devices use 720x720 displays and read input events directly + * from /dev/input/event* devices. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "api.h" +#include "defines.h" +#include "platform.h" +#include "utils.h" + +#include "gl_video.h" +#include "render_sdl2.h" +#include "scaler.h" + +/////////////////////////////// +// Video - Using shared SDL2 backend +/////////////////////////////// + +static SDL2_RenderContext vid_ctx; + +static const SDL2_Config vid_config = { + // No rotation needed (square 720x720 display) + .auto_rotate = 0, + .rotate_cw = 0, + .rotate_null_center = 0, + // Display features + .has_hdmi = 1, + .default_sharpness = SHARPNESS_SOFT, +}; + +SDL_Surface* PLAT_initVideo(void) { + return SDL2_initVideo(&vid_ctx, FIXED_WIDTH, FIXED_HEIGHT, &vid_config); +} + +void PLAT_quitVideo(void) { + SDL2_quitVideo(&vid_ctx); +} + +void PLAT_clearVideo(SDL_Surface* screen) { + SDL2_clearVideo(&vid_ctx); +} + +void PLAT_clearAll(void) { + SDL2_clearAll(&vid_ctx); +} + +SDL_Surface* PLAT_resizeVideo(int w, int h, int p) { + return SDL2_resizeVideo(&vid_ctx, w, h, p); +} + +void PLAT_setVideoScaleClip(int x, int y, int width, int height) {} +void PLAT_setNearestNeighbor(int enabled) {} + +void PLAT_setSharpness(int sharpness) { + SDL2_setSharpness(&vid_ctx, sharpness); +} + +void PLAT_setEffect(int effect) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffect(effect); +} + +void PLAT_setEffectColor(int color) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffectColor(color); +} + +void PLAT_vsync(int remaining) { + SDL2_vsync(remaining); +} + +scaler_t PLAT_getScaler(GFX_Renderer* renderer) { + return SDL2_getScaler(&vid_ctx, renderer); +} + +void PLAT_present(GFX_Renderer* renderer) { + SDL2_present(&vid_ctx, renderer); +} + +SDL_Window* PLAT_getWindow(void) { + return SDL2_getWindow(&vid_ctx); +} + +int PLAT_getRotation(void) { + return SDL2_getRotation(&vid_ctx); +} + +int PLAT_supportsOverscan(void) { + return 1; +} + +/////////////////////////////// +// Input Handling +/////////////////////////////// + +#define RAW_UP 544 +#define RAW_DOWN 545 +#define RAW_LEFT 546 +#define RAW_RIGHT 547 +#define RAW_A 305 +#define RAW_B 304 +#define RAW_X 307 +#define RAW_Y 308 +#define RAW_START 315 +#define RAW_SELECT 314 +#define RAW_MENU 139 +#define RAW_L1 310 +#define RAW_L2 312 +#define RAW_L3 317 +#define RAW_R1 311 +#define RAW_R2 313 +#define RAW_R3 318 +#define RAW_PLUS 115 +#define RAW_MINUS 114 +#define RAW_POWER 116 +#define RAW_HATY 17 +#define RAW_HATX 16 +#define RAW_LSY 1 +#define RAW_LSX 0 +#define RAW_RSY 3 +#define RAW_RSX 4 + +#define RAW_MENU1 RAW_L3 +#define RAW_MENU2 RAW_R3 + +#define INPUT_COUNT 4 +static int inputs[INPUT_COUNT]; + +void PLAT_initInput(void) { + inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + + for (int i = 0; i < INPUT_COUNT; i++) { + if (inputs[i] < 0) + LOG_warn("Failed to open /dev/input/event%d\n", i); + } +} + +void PLAT_quitInput(void) { + for (int i = 0; i < INPUT_COUNT; i++) { + close(inputs[i]); + } +} + +struct input_event { + struct timeval time; + __u16 type; + __u16 code; + __s32 value; +}; +#define EV_KEY 0x01 +#define EV_ABS 0x03 + +void PLAT_pollInput(void) { + uint32_t tick = SDL_GetTicks(); + PAD_beginPolling(); + PAD_handleRepeat(tick); + + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type != EV_KEY && event.type != EV_ABS) + continue; + + int btn = BTN_NONE; + int pressed = 0; + int type = event.type; + int code = event.code; + int value = event.value; + + if (type == EV_KEY) { + if (value > 1) + continue; + pressed = value; + if (code == RAW_UP) { + btn = BTN_DPAD_UP; + } else if (code == RAW_DOWN) { + btn = BTN_DPAD_DOWN; + } else if (code == RAW_LEFT) { + btn = BTN_DPAD_LEFT; + } else if (code == RAW_RIGHT) { + btn = BTN_DPAD_RIGHT; + } else if (code == RAW_A) { + btn = BTN_A; + } else if (code == RAW_B) { + btn = BTN_B; + } else if (code == RAW_X) { + btn = BTN_X; + } else if (code == RAW_Y) { + btn = BTN_Y; + } else if (code == RAW_START) { + btn = BTN_START; + } else if (code == RAW_SELECT) { + btn = BTN_SELECT; + } else if (code == RAW_MENU) { + btn = BTN_MENU; + } else if (code == RAW_MENU1) { + btn = BTN_MENU; + } else if (code == RAW_MENU2) { + btn = BTN_MENU; + } else if (code == RAW_L1) { + btn = BTN_L1; + } else if (code == RAW_L2) { + btn = BTN_L2; + } else if (code == RAW_L3) { + btn = BTN_L3; + } else if (code == RAW_R1) { + btn = BTN_R1; + } else if (code == RAW_R2) { + btn = BTN_R2; + } else if (code == RAW_R3) { + btn = BTN_R3; + } else if (code == RAW_PLUS) { + btn = BTN_PLUS; + } else if (code == RAW_MINUS) { + btn = BTN_MINUS; + } else if (code == RAW_POWER) { + btn = BTN_POWER; + } + } else if (type == EV_ABS) { + if (code == RAW_LSX) { + pad.laxis.x = (value * 32767) / 1800; + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, pad.laxis.x, + tick + PAD_REPEAT_DELAY); + } else if (code == RAW_LSY) { + pad.laxis.y = (value * 32767) / 1800; + PAD_setAnalog(BTN_ID_ANALOG_UP, BTN_ID_ANALOG_DOWN, pad.laxis.y, + tick + PAD_REPEAT_DELAY); + // Right stick axes are swapped in hardware + } else if (code == RAW_RSX) { + pad.raxis.y = (value * 32767) / 1800; + } else if (code == RAW_RSY) { + pad.raxis.x = (value * 32767) / 1800; + } + btn = BTN_NONE; + } + + PAD_updateButton(btn, pressed, tick); + } + } +} + +int PLAT_shouldWake(void) { + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) + return 1; + } + } + return 0; +} + +/////////////////////////////// +// Power Management +/////////////////////////////// + +static int online = 0; + +void PLAT_getBatteryStatus(int* is_charging, int* charge) { + *is_charging = getInt("/sys/class/power_supply/ac/online"); + + int i = getInt("/sys/class/power_supply/battery/capacity"); + if (i > 80) + *charge = 100; + else if (i > 60) + *charge = 80; + else if (i > 40) + *charge = 60; + else if (i > 20) + *charge = 40; + else if (i > 10) + *charge = 20; + else + *charge = 10; + + char status[16]; + getFile("/sys/class/net/wlan0/operstate", status, 16); + online = prefixMatch("up", status); +} + +void PLAT_enableBacklight(int enable) { + putInt("/sys/class/backlight/backlight/bl_power", + enable ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN); +} + +void PLAT_powerOff(void) { + sleep(2); + SetRawVolume(MUTE_VOLUME_RAW); + PLAT_enableBacklight(0); + SND_quit(); + VIB_quit(); + PWR_quit(); + GFX_quit(); + system("shutdown"); + while (1) + pause(); +} + +double PLAT_getDisplayHz(void) { + return SDL2_getDisplayHz(); +} + +uint32_t PLAT_measureVsyncInterval(void) { + return SDL2_measureVsyncInterval(&vid_ctx); +} + +#define GOVERNOR_PATH "/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed" + +void PLAT_setCPUSpeed(int speed) { + int freq = 0; + switch (speed) { + case CPU_SPEED_IDLE: + freq = 408000; // 20% of max (408 MHz) + break; + case CPU_SPEED_POWERSAVE: + freq = 1104000; // 55% of max (1104 MHz) + break; + case CPU_SPEED_NORMAL: + freq = 1608000; // 80% of max (1608 MHz) + break; + case CPU_SPEED_PERFORMANCE: + freq = 1992000; // 100% (1992 MHz) + break; + } + putInt(GOVERNOR_PATH, freq); +} + +/** + * Gets available CPU frequencies from sysfs. + * + * @param frequencies Output array to fill with frequencies (in kHz) + * @param max_count Maximum number of frequencies to return + * @return Number of frequencies found + */ +int PLAT_getAvailableCPUFrequencies(int* frequencies, int max_count) { + return PWR_getAvailableCPUFrequencies_sysfs(frequencies, max_count); +} + +/** + * Sets CPU frequency directly via sysfs. + * + * @param freq_khz Target frequency in kHz + * @return 0 on success, -1 on failure + */ +int PLAT_setCPUFrequency(int freq_khz) { + return PWR_setCPUFrequency_sysfs(freq_khz); +} + +void PLAT_setRumble(int strength) {} + +int PLAT_pickSampleRate(int requested, int max) { + return MIN(requested, max); +} + +static char model[256]; +char* PLAT_getModel(void) { + char buffer[256]; + getFile("/proc/device-tree/model", buffer, 256); + char* tmp = strrchr(buffer, ' '); + if (tmp) { + strncpy(model, tmp + 1, sizeof(model) - 1); + model[sizeof(model) - 1] = '\0'; + } else { + strncpy(model, "RGB30", sizeof(model) - 1); + model[sizeof(model) - 1] = '\0'; + } + return model; +} + +int PLAT_isOnline(void) { + return online; +} diff --git a/workspace/rk3566/platform/platform.h b/workspace/rk3566/platform/platform.h new file mode 100644 index 00000000..443c1403 --- /dev/null +++ b/workspace/rk3566/platform/platform.h @@ -0,0 +1,222 @@ +/** + * RK3566/platform/platform.h - Platform definitions for Rockchip RK3566 devices + * + * Supported devices (Rockchip RK3566, Cortex-A55): + * - PowKiddy RGB30: 4.0" 720x720 display + * - Anbernic RG353P/M/V/VS: Various display configurations + * + * Hardware features: + * - 720x720 square display (1:1 aspect ratio) + * - 1280x720 HDMI output support + * - D-pad and face buttons (A/B/X/Y) + * - Shoulder buttons (L1/R1/L2/R2) + * - Menu buttons (primary and alternate) + * - Uses hybrid input (minimal SDL keyboard + joystick) + * - Larger UI with increased row count and padding + * + * @note Power button uses SDL keyboard mapping, volume controls use evdev codes + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "rk3566" + +/////////////////////////////// +// Hardware Capabilities +/////////////////////////////// + +#define HAS_OPENGLES 1 // Mali-G52 GPU supports OpenGL ES 2.0 + +/////////////////////////////// +// Audio Configuration +/////////////////////////////// + +// Uses default SND_RATE_CONTROL_D (0.012f) for standard timing + +/////////////////////////////// +// Video Buffer Scaling +/////////////////////////////// + +// Uses default BUFFER_SCALE_FACTOR (1.0f) - GPU hardware scaler handles all scaling + +/////////////////////////////// +// UI Scaling +/////////////////////////////// + +// Uses default SCALE_MODIFIER (1.0f) and EDGE_PADDING (10) for standard layout + +/////////////////////////////// +// Dependencies +/////////////////////////////// + +#include "sdl.h" + +/////////////////////////////// +// Platform Variant Detection +/////////////////////////////// + +// No device variants (single hardware configuration) + +/////////////////////////////// +// SDL Keyboard Button Mappings +// RK3566 devices use minimal SDL keyboard input (power button only) +/////////////////////////////// + +#define BUTTON_UP BUTTON_NA +#define BUTTON_DOWN BUTTON_NA +#define BUTTON_LEFT BUTTON_NA +#define BUTTON_RIGHT BUTTON_NA + +#define BUTTON_SELECT BUTTON_NA +#define BUTTON_START BUTTON_NA + +#define BUTTON_A BUTTON_NA +#define BUTTON_B BUTTON_NA +#define BUTTON_X BUTTON_NA +#define BUTTON_Y BUTTON_NA + +#define BUTTON_L1 BUTTON_NA +#define BUTTON_R1 BUTTON_NA +#define BUTTON_L2 BUTTON_NA +#define BUTTON_R2 BUTTON_NA +#define BUTTON_L3 BUTTON_NA +#define BUTTON_R3 BUTTON_NA + +#define BUTTON_MENU BUTTON_NA +#define BUTTON_MENU_ALT BUTTON_NA +#define BUTTON_POWER SDLK_POWER // Power key mapped to SDL +#define BUTTON_PLUS BUTTON_NA // Available but not used (commented: SDLK_RSUPER) +#define BUTTON_MINUS BUTTON_NA // Available but not used (commented: SDLK_LSUPER) + +/////////////////////////////// +// Evdev/Keyboard Input Codes +// Hardware keycodes from kernel input subsystem +/////////////////////////////// + +#define CODE_UP CODE_NA +#define CODE_DOWN CODE_NA +#define CODE_LEFT CODE_NA +#define CODE_RIGHT CODE_NA + +#define CODE_SELECT CODE_NA +#define CODE_START CODE_NA + +#define CODE_A CODE_NA +#define CODE_B CODE_NA +#define CODE_X CODE_NA +#define CODE_Y CODE_NA + +#define CODE_L1 CODE_NA +#define CODE_R1 CODE_NA +#define CODE_L2 CODE_NA +#define CODE_R2 CODE_NA +#define CODE_L3 CODE_NA +#define CODE_R3 CODE_NA + +#define CODE_MENU CODE_NA +#define CODE_MENU_ALT CODE_NA +#define CODE_POWER 102 // KEY_HOME + +#define CODE_PLUS 129 // Volume up (swapped from keymon) +#define CODE_MINUS 128 // Volume down (swapped from keymon) + +/////////////////////////////// +// Joystick Button Mappings +// Hardware joystick indices +/////////////////////////////// + +#define JOY_UP 13 +#define JOY_DOWN 14 +#define JOY_LEFT 15 +#define JOY_RIGHT 16 + +#define JOY_SELECT 8 +#define JOY_START 9 + +#define JOY_A 1 +#define JOY_B 0 +#define JOY_X 2 +#define JOY_Y 3 + +#define JOY_L1 4 +#define JOY_R1 5 +#define JOY_L2 6 +#define JOY_R2 7 +#define JOY_L3 JOY_NA +#define JOY_R3 JOY_NA + +#define JOY_MENU 11 +#define JOY_MENU_ALT 12 // Secondary menu button +#define JOY_POWER JOY_NA +#define JOY_PLUS JOY_NA +#define JOY_MINUS JOY_NA + +/////////////////////////////// +// Function Button Mappings +// System-level button combinations +/////////////////////////////// + +#define BTN_RESUME BTN_X // Button to resume from save state +#define BTN_SLEEP BTN_POWER // Button to enter sleep mode +#define BTN_WAKE BTN_POWER // Button to wake from sleep +#define BTN_MOD_VOLUME BTN_NONE // Modifier for volume control (none - direct buttons) +#define BTN_MOD_BRIGHTNESS BTN_MENU // Hold MENU for brightness control +#define BTN_MOD_PLUS BTN_PLUS // Increase with PLUS +#define BTN_MOD_MINUS BTN_MINUS // Decrease with MINUS + +/////////////////////////////// +// Display Specifications +/////////////////////////////// + +#define SCREEN_DIAGONAL 4.0f // Physical screen diagonal in inches +#define FIXED_WIDTH 720 // Screen width in pixels (square display) +#define FIXED_HEIGHT 720 // Screen height in pixels (1:1 aspect ratio) + +/////////////////////////////// +// HDMI Output Specifications +/////////////////////////////// + +#define HAS_HDMI 1 // HDMI output supported +#define HDMI_WIDTH 1280 // HDMI width in pixels +#define HDMI_HEIGHT 720 // HDMI height in pixels (720p) + +/////////////////////////////// +// Platform-Specific Paths and Settings +/////////////////////////////// + +#define SDCARD_PATH \ + "/storage" // Path to SD card mount point (LessOS default, overridden by LESSOS_STORAGE) +#define MUTE_VOLUME_RAW 0 // Raw value for muted volume + +/////////////////////////////// +// Keymon Configuration +/////////////////////////////// + +#define KEYMON_BUTTON_MENU 317 +#define KEYMON_BUTTON_MENU_ALT 318 +#define KEYMON_BUTTON_PLUS 114 +#define KEYMON_BUTTON_MINUS 115 + +#define KEYMON_HAS_HDMI 1 +#define KEYMON_HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +#define KEYMON_HAS_JACK 1 +#define KEYMON_JACK_STATE_PATH "/sys/bus/platform/devices/singleadc-joypad/hp" + +// Uses event0-4 plus js0 (joystick) for menu button detection +#define KEYMON_INPUT_COUNT 6 +#define KEYMON_INPUT_DEVICE_0 "/dev/input/event0" +#define KEYMON_INPUT_DEVICE_1 "/dev/input/event1" +#define KEYMON_INPUT_DEVICE_2 "/dev/input/event2" +#define KEYMON_INPUT_DEVICE_3 "/dev/input/event3" +#define KEYMON_INPUT_DEVICE_4 "/dev/input/event4" +#define KEYMON_INPUT_DEVICE_5 "/dev/input/js0" + +/////////////////////////////// + +#endif diff --git a/workspace/rk3566/show/Makefile b/workspace/rk3566/show/Makefile new file mode 100755 index 00000000..03195e74 --- /dev/null +++ b/workspace/rk3566/show/Makefile @@ -0,0 +1,14 @@ +ifeq (,$(CROSS_COMPILE)) +$(error missing CROSS_COMPILE for this toolchain) +endif + +TARGET = show +PRODUCT = $(TARGET).elf + +CC = $(CROSS_COMPILE)gcc +FLAGS = -Os $(CFLAGS) $(LDFLAGS) -lSDL2 -lSDL2_image -lrt -ldl -Wl,--gc-sections -s + +all: + $(CC) $(TARGET).c -o $(PRODUCT) $(FLAGS) +clean: + rm -rf $(PRODUCT) \ No newline at end of file diff --git a/workspace/rk3566/show/show.c b/workspace/rk3566/show/show.c new file mode 100644 index 00000000..d095d569 --- /dev/null +++ b/workspace/rk3566/show/show.c @@ -0,0 +1,83 @@ +// rk3566 - Display an image on screen during boot/install/update +#include +#include +#include +#include +#include +#include + +SDL_Window* window; +SDL_Surface* screen; + +int main(int argc, char* argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: show.elf image.png [delay]\n"); + return 1; + } + + char path[256]; + snprintf(path, sizeof(path), "%s", argv[1]); + if (access(path, F_OK) != 0) { + fprintf(stderr, "show.elf: Image not found: %s\n", path); + return 1; + } + + int delay = argc > 2 ? atoi(argv[2]) : 2; + + fprintf(stderr, "show.elf: Initializing SDL2...\n"); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "show.elf: SDL_Init failed: %s\n", SDL_GetError()); + return 1; + } + + SDL_ShowCursor(0); + + // Use 0,0 to let SDL auto-detect display size (like tg5040) + fprintf(stderr, "show.elf: Creating window...\n"); + window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_SHOWN); + if (!window) { + fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + screen = SDL_GetWindowSurface(window); + if (!screen) { + fprintf(stderr, "show.elf: SDL_GetWindowSurface failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Window size: %dx%d\n", screen->w, screen->h); + SDL_FillRect(screen, NULL, 0); + + fprintf(stderr, "show.elf: Loading image: %s\n", path); + SDL_Surface* img = IMG_Load(path); + if (!img) { + fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Image size: %dx%d\n", img->w, img->h); + + // Center the image on screen + SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; + SDL_BlitSurface(img, NULL, screen, &dst); + + if (SDL_UpdateWindowSurface(window) < 0) { + fprintf(stderr, "show.elf: SDL_UpdateWindowSurface failed: %s\n", SDL_GetError()); + } + + fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); + sleep(delay); + + SDL_FreeSurface(img); + SDL_DestroyWindow(window); + SDL_Quit(); + + fprintf(stderr, "show.elf: Done\n"); + return 0; +}