// ============================================================================ // LUA GAME LOADER - IMPLEMENTATION // ============================================================================ // Discovers Lua scripts on SD card and integrates with game launcher #include "lua_game_loader.h" #include "lua_game.h" #include "sd_card.h" #include #include #include extern "C" { #include "ff.h" } // Structure to hold script path for factory closure struct LuaGameFactoryData { char script_path[256]; char name[64]; char description[128]; }; static std::vector factory_data_list; // Factory wrapper that captures script path static Game* lua_game_factory_wrapper(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, void* user_data) { LuaGameFactoryData* data = (LuaGameFactoryData*)user_data; return new LuaGame(data->script_path, width, height, renderer, gui, input_manager); } bool LuaGameLoader::parse_metadata(const char* script_path, char* name, char* description) { FIL fil; FRESULT fr; // Default name from filename const char* filename = strrchr(script_path, '/'); if (filename) { filename++; } else { filename = script_path; } // Remove .lua extension for default name strncpy(name, filename, 63); name[63] = '\0'; char* ext = strstr(name, ".lua"); if (ext) *ext = '\0'; // Default empty description description[0] = '\0'; // Set SPI speed for SD card operations uint prev_speed = sd_card_set_spi_speed(); // Try to open file and parse metadata comments fr = f_open(&fil, script_path, FA_READ); if (fr != FR_OK) { printf("LuaGameLoader: Warning - could not open %s for metadata\n", script_path); sd_card_restore_spi_speed(prev_speed); return false; } // Read first 512 bytes to look for metadata comments char buffer[512]; UINT bytes_read; fr = f_read(&fil, buffer, sizeof(buffer) - 1, &bytes_read); f_close(&fil); // Restore SPI speed sd_card_restore_spi_speed(prev_speed); if (fr != FR_OK) { return false; } buffer[bytes_read] = '\0'; // Parse metadata comments: -- NAME: Game Name char* line = buffer; while (line && (line - buffer) < bytes_read) { char* next_line = strchr(line, '\n'); if (next_line) *next_line = '\0'; // Check for -- NAME: if (strncmp(line, "-- NAME:", 8) == 0) { const char* value = line + 8; while (*value == ' ') value++; // Skip spaces strncpy(name, value, 63); name[63] = '\0'; } // Check for -- DESC: else if (strncmp(line, "-- DESC:", 8) == 0) { const char* value = line + 8; while (*value == ' ') value++; strncpy(description, value, 127); description[127] = '\0'; } if (next_line) { line = next_line + 1; } else { break; } } return true; } int LuaGameLoader::register_all_games(GameLauncher* launcher) { DIR dir; FILINFO fno; FRESULT fr; int count = 0; printf("LuaGameLoader: Scanning /games directory for .lua scripts...\n"); // Set SPI speed for SD card operations uint prev_speed = sd_card_set_spi_speed(); // Open /games directory fr = f_opendir(&dir, "/games"); if (fr != FR_OK) { printf("LuaGameLoader: Could not open /games directory (error %d)\n", fr); printf("LuaGameLoader: Make sure SD card is mounted and /games exists\n"); sd_card_restore_spi_speed(prev_speed); return 0; } // Scan for .lua files while (true) { fr = f_readdir(&dir, &fno); if (fr != FR_OK) { printf("LuaGameLoader: Error reading directory (error %d)\n", fr); break; } if (fno.fname[0] == 0) { printf("LuaGameLoader: End of directory reached\n"); break; // End of directory } printf("LuaGameLoader: Found entry: %s (attrib=0x%02X)\n", fno.fname, fno.fattrib); // Skip directories if (fno.fattrib & AM_DIR) { printf("LuaGameLoader: Skipping directory\n"); continue; } // Skip hidden files (these are short 8.3 filename entries) if (fno.fattrib & AM_HID) { printf("LuaGameLoader: Skipping hidden file (short filename entry)\n"); continue; } // Check for .lua extension (case-insensitive) size_t len = strlen(fno.fname); printf("LuaGameLoader: Filename length: %d\n", len); if (len < 5) { printf("LuaGameLoader: Filename too short\n"); continue; } // Case-insensitive check for .lua or .LUA const char* ext = fno.fname + len - 4; if (strcasecmp(ext, ".lua") != 0) { printf("LuaGameLoader: Not a .lua file (ext=%s)\n", ext); continue; } printf("LuaGameLoader: Valid .lua file!\n"); // Build full path char script_path[256]; snprintf(script_path, sizeof(script_path), "/games/%s", fno.fname); // Create factory data (persistent for game lifetime) LuaGameFactoryData* data = new LuaGameFactoryData(); strncpy(data->script_path, script_path, sizeof(data->script_path) - 1); data->script_path[sizeof(data->script_path) - 1] = '\0'; // Parse metadata directly into persistent storage parse_metadata(script_path, data->name, data->description); printf("LuaGameLoader: Found %s - '%s'\n", fno.fname, data->name); factory_data_list.push_back(data); // Register with launcher - using lambda factory pattern launcher->register_game( data->name, data->description[0] ? data->description : "Lua Script", [data](uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager) -> Game* { return new LuaGame(data->script_path, width, height, renderer, gui, input_manager); } ); count++; } f_closedir(&dir); // Restore SPI speed sd_card_restore_spi_speed(prev_speed); printf("LuaGameLoader: Registered %d Lua games\n", count); return count; }