From ca4c7218d6ae14e0d5b76e74b35db5db3a422619 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 20:30:51 +0000 Subject: [PATCH 01/10] pico: split out file extension helper --- 32blit-pico/blit_launch.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index 590b31c66..dbe50efc4 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -190,6 +190,18 @@ static bool cleanup_duplicates(RawMetadata &meta, uint32_t new_offset) { return ret; } +// returns lowercased extension +static std::string get_file_ext(const char *path) { + // get the extension + std::string_view sv(path); + auto last_dot = sv.find_last_of('.'); + auto ext = last_dot == std::string::npos ? "" : std::string(sv.substr(last_dot + 1)); + for(auto &c : ext) + c = tolower(c); + + return ext; +} + // 32blit API RawMetadata *get_running_game_metadata() { @@ -286,11 +298,7 @@ blit::CanLaunchResult can_launch(const char *path) { } // get the extension - std::string_view sv(path); - auto last_dot = sv.find_last_of('.'); - auto ext = last_dot == std::string::npos ? "" : std::string(sv.substr(last_dot + 1)); - for(auto &c : ext) - c = tolower(c); + auto ext = get_file_ext(path); if(ext == "blit") { BlitGameHeader header; From c77e19ca7d98709d6b0195dba7cf62ab4c274280 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 20:42:12 +0000 Subject: [PATCH 02/10] pico: build type handler list --- 32blit-pico/blit_launch.cpp | 38 +++++++++++++++++++++++++++++++++++++ 32blit-pico/blit_launch.hpp | 2 ++ 32blit-pico/main.cpp | 4 +++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index dbe50efc4..d922b14e9 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -1,4 +1,5 @@ #include +#include #include "pico/stdlib.h" #include "hardware/flash.h" @@ -34,6 +35,13 @@ static const uint32_t flash_end = PICO_FLASH_SIZE_BYTES; static const uint32_t flash_end = FLASH_STORAGE_OFFSET; #endif +struct TypeHandlerInfo { + char type[4]; // should have a null terminator, but the low byte of the offset will always be zero + uint32_t offset; +}; + +std::forward_list handlers; + extern int (*do_tick)(uint32_t time); void disable_user_code(); @@ -223,6 +231,36 @@ RawMetadata *get_running_game_metadata() { return nullptr; } +void create_type_handler_list() { + handlers.clear(); + + blit::api.list_installed_games([](const uint8_t *ptr, uint32_t block, uint32_t size) { + auto header = (const BlitGameHeader *)ptr; + auto metadata_ptr = ptr + header->end; + + if(memcmp(metadata_ptr, "BLITMETA", 8) != 0) + return; + + // skip straight to the type block + metadata_ptr += 10 + sizeof(RawMetadata); + + if(memcmp(metadata_ptr, "BLITTYPE", 8) != 0) + return; + + // get the type block + auto type_meta = (const RawTypeMetadata *)(metadata_ptr + 8); + + // loop through filetypes + for(unsigned i = 0; i < type_meta->num_filetypes; i++) { + TypeHandlerInfo info; + memcpy(info.type, type_meta->filetypes[i], 4); + info.offset = block * game_block_size; + + handlers.emplace_front(info); + } + }); +} + bool launch_file(const char *path) { uint32_t flash_offset = ~0u; diff --git a/32blit-pico/blit_launch.hpp b/32blit-pico/blit_launch.hpp index 69c203fbc..abb7dce55 100644 --- a/32blit-pico/blit_launch.hpp +++ b/32blit-pico/blit_launch.hpp @@ -10,6 +10,8 @@ static constexpr unsigned int game_block_size = 64 * 1024; RawMetadata *get_running_game_metadata(); +void create_type_handler_list(); + bool launch_file(const char *path); blit::CanLaunchResult can_launch(const char *path); void launch_pre_init(); diff --git a/32blit-pico/main.cpp b/32blit-pico/main.cpp index aee91114d..4dba8e2af 100644 --- a/32blit-pico/main.cpp +++ b/32blit-pico/main.cpp @@ -317,7 +317,9 @@ int main() { #endif #endif -#ifndef BUILD_LOADER +#ifdef BUILD_LOADER + create_type_handler_list(); +#else blit::set_screen_mode(ScreenMode::lores); #endif From 85bfd98f02115a16d9ee0222cfda46cb2712f939 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 20:45:14 +0000 Subject: [PATCH 03/10] pico: implement get_type_handler_metadata --- 32blit-pico/blit_launch.cpp | 17 +++++++++++++++++ 32blit-pico/blit_launch.hpp | 2 ++ 32blit-pico/main.cpp | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index d922b14e9..0cafd7046 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -468,6 +468,23 @@ void erase_game(uint32_t offset) { #endif } +void *get_type_handler_metadata(const char *filetype) { +#ifdef BUILD_LOADER + for(auto &handler : handlers) { + if(strncmp(filetype, handler.type, 4) == 0) { + auto ptr = (const uint8_t *)(FLASH_BASE + handler.offset); + auto header = (const BlitGameHeader *)ptr; + auto metadata_ptr = ptr + header->end; + + // this really shouldn't fail if it's in the list, but be safe + if(memcmp(metadata_ptr, "BLITMETA", 8) == 0) + return (void *)metadata_ptr; + } + } +#endif + return nullptr; +} + // .blit file writer void BlitWriter::init(uint32_t file_len) { this->file_len = file_len; diff --git a/32blit-pico/blit_launch.hpp b/32blit-pico/blit_launch.hpp index abb7dce55..e79bdde36 100644 --- a/32blit-pico/blit_launch.hpp +++ b/32blit-pico/blit_launch.hpp @@ -21,6 +21,8 @@ void list_installed_games(std::function Date: Mon, 9 Mar 2026 20:46:44 +0000 Subject: [PATCH 04/10] pico: handle handlers in launch_file and can_launch No launch path yet --- 32blit-pico/blit_launch.cpp | 89 +++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index 0cafd7046..e8543ae6c 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -267,53 +267,68 @@ bool launch_file(const char *path) { if(strncmp(path, "flash:/", 7) == 0) // from flash flash_offset = atoi(path + 7) * game_block_size; else { - // from storage - auto file = open_file(path, blit::OpenMode::read); - if(!file) - return false; + auto ext = get_file_ext(path); - // read file metadata and try to find matching installed gat - RawMetadata meta; - RawTypeMetadata type_meta = {}; + if(ext != "blit") { + // with handler + for(auto &handler : handlers) { + if(strncmp(ext.data(), handler.type, 4) == 0) { + flash_offset = handler.offset; + break; + } + } - if(read_file_metadata(file, meta, type_meta)) - flash_offset = find_installed_blit(meta); + // set launch path + } else { + // from storage + auto file = open_file(path, blit::OpenMode::read); - // flash if not found - if(flash_offset == ~0u) { - BlitWriter writer; + if(!file) + return false; - uint32_t file_offset = 0; - uint32_t len = get_file_length(file); + // read file metadata and try to find matching installed gat + RawMetadata meta; + RawTypeMetadata type_meta = {}; - writer.init(len); + if(read_file_metadata(file, meta, type_meta)) + flash_offset = find_installed_blit(meta); - // read in small chunks - uint8_t buf[FLASH_PAGE_SIZE]; + // flash if not found + if(flash_offset == ~0u) { + BlitWriter writer; - while(file_offset < len) { - auto bytes_read = read_file(file, file_offset, FLASH_PAGE_SIZE, (char *)buf); - if(bytes_read <= 0) - break; + uint32_t file_offset = 0; + uint32_t len = get_file_length(file); - if(!writer.write(buf, bytes_read)) - break; + writer.init(len); - file_offset += bytes_read; - } + // read in small chunks + uint8_t buf[FLASH_PAGE_SIZE]; - close_file(file); + while(file_offset < len) { + auto bytes_read = read_file(file, file_offset, FLASH_PAGE_SIZE, (char *)buf); + if(bytes_read <= 0) + break; - // didn't write everything, fail launch - if(writer.get_remaining() > 0) - return false; + if(!writer.write(buf, bytes_read)) + break; - flash_offset = writer.get_flash_offset(); + file_offset += bytes_read; + } - cleanup_duplicates(meta, flash_offset); - } else - close_file(file); + close_file(file); + + // didn't write everything, fail launch + if(writer.get_remaining() > 0) + return false; + + flash_offset = writer.get_flash_offset(); + + cleanup_duplicates(meta, flash_offset); + } else + close_file(file); + } } auto header = (BlitGameHeader *)(FLASH_BASE + flash_offset); @@ -355,6 +370,14 @@ blit::CanLaunchResult can_launch(const char *path) { close_file(file); return blit::CanLaunchResult::IncompatibleBlit; } + + // check handlers + for(auto &handler : handlers) { + if(strncmp(ext.data(), handler.type, 4) == 0) { + return blit::CanLaunchResult::Success; + } + } + #endif return blit::CanLaunchResult::UnknownType; From af9b634998485ca3712688ff5a74abcf95628451 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 20:53:57 +0000 Subject: [PATCH 05/10] pico: remove erased handlers from list --- 32blit-pico/blit_launch.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index e8543ae6c..19ff002bc 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -488,6 +488,15 @@ void erase_game(uint32_t offset) { restore_interrupts(status); set_render_overlay_enabled(false); + + // clear out any handler entries + auto prev = handlers.before_begin(); + for(auto it = handlers.begin(); it != handlers.end();) { + if(it->offset == offset) + it = handlers.erase_after(prev); + else + prev = it++; + } #endif } From 110d3b5f9906901c1efc878d5a8d5db29d9d14a6 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 21:00:21 +0000 Subject: [PATCH 06/10] pico: stub launch file if we're not the loader --- 32blit-pico/blit_launch.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index 19ff002bc..cc0d8b43b 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -262,6 +262,7 @@ void create_type_handler_list() { } bool launch_file(const char *path) { +#ifdef BUILD_LOADER uint32_t flash_offset = ~0u; if(strncmp(path, "flash:/", 7) == 0) // from flash @@ -341,6 +342,9 @@ bool launch_file(const char *path) { requested_launch_offset = flash_offset; return true; +#else + return false; +#endif } blit::CanLaunchResult can_launch(const char *path) { From 66ba57d5c5c8b4f66ca74b316aac94a52384d7de Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 22:16:17 +0000 Subject: [PATCH 07/10] pico: implement launch path --- 32blit-pico/blit_launch.cpp | 16 ++++++++++++++++ 32blit-pico/blit_launch.hpp | 2 ++ 32blit-pico/main.cpp | 4 ---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index cc0d8b43b..1cfc3334a 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -42,6 +42,10 @@ struct TypeHandlerInfo { std::forward_list handlers; +#ifdef BUILD_LOADER +static char launch_path[256]; +#endif + extern int (*do_tick)(uint32_t time); void disable_user_code(); @@ -265,6 +269,9 @@ bool launch_file(const char *path) { #ifdef BUILD_LOADER uint32_t flash_offset = ~0u; + // clear old path + launch_path[0] = 0; + if(strncmp(path, "flash:/", 7) == 0) // from flash flash_offset = atoi(path + 7) * game_block_size; else { @@ -281,6 +288,7 @@ bool launch_file(const char *path) { } // set launch path + strncpy(launch_path, path, sizeof(launch_path) - 1); } else { // from storage auto file = open_file(path, blit::OpenMode::read); @@ -521,6 +529,14 @@ void *get_type_handler_metadata(const char *filetype) { return nullptr; } +const char *get_launch_path() { +#ifdef BUILD_LOADER + return launch_path; +#else + return nullptr; +#endif +} + // .blit file writer void BlitWriter::init(uint32_t file_len) { this->file_len = file_len; diff --git a/32blit-pico/blit_launch.hpp b/32blit-pico/blit_launch.hpp index e79bdde36..4f174f3ad 100644 --- a/32blit-pico/blit_launch.hpp +++ b/32blit-pico/blit_launch.hpp @@ -23,6 +23,8 @@ void erase_game(uint32_t offset); void *get_type_handler_metadata(const char *filetype); +const char *get_launch_path(); + class BlitWriter final { public: void init(uint32_t file_len); diff --git a/32blit-pico/main.cpp b/32blit-pico/main.cpp index dc1eae97e..defd91fa1 100644 --- a/32blit-pico/main.cpp +++ b/32blit-pico/main.cpp @@ -60,10 +60,6 @@ static uint32_t get_max_us_timer() { return 0xFFFFFFFF; // it's a 64bit timer... } -const char *get_launch_path() { - return nullptr; -} - static GameMetadata get_metadata() { GameMetadata ret; From 1fb605edfb75c3dafd4e90cf7e5bfd1dbcb7090c Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Mon, 9 Mar 2026 22:28:05 +0000 Subject: [PATCH 08/10] pico: close open files when resetting the loader Now daftboy works more than twice --- 32blit-pico/main.cpp | 2 ++ 32blit-pico/usb.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/32blit-pico/main.cpp b/32blit-pico/main.cpp index defd91fa1..0cd6606b4 100644 --- a/32blit-pico/main.cpp +++ b/32blit-pico/main.cpp @@ -215,6 +215,8 @@ void disable_user_code() { static int64_t home_hold_callback(alarm_id_t id, void *user_data) { home_hold_alarm_id = 0; + close_open_files(); + launch_pre_init(); ::init(); // re-initialising the loader is effectively a reset diff --git a/32blit-pico/usb.cpp b/32blit-pico/usb.cpp index d8233742e..53d8238bc 100644 --- a/32blit-pico/usb.cpp +++ b/32blit-pico/usb.cpp @@ -10,6 +10,8 @@ #include "multiplayer.hpp" #include "overlay.hpp" +#include "fatfs_blit_api.hpp" + #include "engine/engine.hpp" #include "executable.hpp" @@ -173,6 +175,7 @@ class CDCProgCommand final : public CDCCommand { usb_cdc_flush_write(); // reinit loader + close_open_files(); ::init(); return Status::Done; From 225afb9e08577019c25e147aeeb833385495ee9f Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Tue, 10 Mar 2026 11:22:08 +0000 Subject: [PATCH 09/10] pico: more #ifdefs around loader helpers --- 32blit-pico/blit_launch.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index 1cfc3334a..544ae47ee 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -78,6 +78,8 @@ static uint32_t calc_num_blocks(uint32_t size) { return (size - 1) / game_block_size + 1; } +#ifdef BUILD_LOADER + #ifdef PICO_RP2350 static uint32_t find_flash_offset(uint32_t requested_size) { uint32_t free_off = 0; // 0 is invalid as that's where the loader is @@ -175,6 +177,20 @@ static uint32_t find_installed_blit(RawMetadata &meta) { return ~0u; } +// returns lowercased extension +static std::string get_file_ext(const char *path) { + // get the extension + std::string_view sv(path); + auto last_dot = sv.find_last_of('.'); + auto ext = last_dot == std::string::npos ? "" : std::string(sv.substr(last_dot + 1)); + for(auto &c : ext) + c = tolower(c); + + return ext; +} + +#endif + static bool cleanup_duplicates(RawMetadata &meta, uint32_t new_offset) { bool ret = false; for(uint32_t off = 0; off < flash_end;) { @@ -202,18 +218,6 @@ static bool cleanup_duplicates(RawMetadata &meta, uint32_t new_offset) { return ret; } -// returns lowercased extension -static std::string get_file_ext(const char *path) { - // get the extension - std::string_view sv(path); - auto last_dot = sv.find_last_of('.'); - auto ext = last_dot == std::string::npos ? "" : std::string(sv.substr(last_dot + 1)); - for(auto &c : ext) - c = tolower(c); - - return ext; -} - // 32blit API RawMetadata *get_running_game_metadata() { From e968b86c037d59e88c1369a8e287f9598b7644fb Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Wed, 11 Mar 2026 18:54:34 +0000 Subject: [PATCH 10/10] pico: add #ifdef BLIT_LOADER around BlitWriter Unbreaks non-loader RP2350 builds. --- 32blit-pico/blit_launch.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp index 544ae47ee..a54c5453d 100644 --- a/32blit-pico/blit_launch.cpp +++ b/32blit-pico/blit_launch.cpp @@ -541,6 +541,8 @@ const char *get_launch_path() { #endif } +#ifdef BUILD_LOADER + // .blit file writer void BlitWriter::init(uint32_t file_len) { this->file_len = file_len; @@ -639,3 +641,5 @@ bool BlitWriter::prepare_write(const uint8_t *buf) { return true; } + +#endif