Files
basic1/games/lua_game_loader.cpp
Adolfo Reyna 84b009c33e Add serial upload tool for rapid Lua game iteration
Implements a complete serial upload workflow that allows uploading and
immediately testing Lua games via USB serial connection.

New Components:
- SerialUploader: Receives files via serial, writes to SD card
- upload_game.py: Python tool for sending files from host computer
- Protocol: Text-based with base64 encoding for reliability

Key Features:
- Uploads file to /games folder on SD card
- Overwrites existing files (FA_CREATE_ALWAYS)
- Auto-launches uploaded game immediately
- Proper memory cleanup (prevents Lua state conflicts)

SD Card Fixes:
- Fixed SPI speed management (12.5MHz for SD, 32MHz for display)
- Fixed SD write protocol (poll for data response token)
- Added speed switching wrappers around all FatFS operations
- Cleaned up excessive debug output

Game Launcher Improvements:
- Added clear_games() to prevent duplicate registrations
- Added cleanup in select_game_by_name() to delete old instances
- Added exact match priority in game selection
- LuaGameLoader now has clear_factory_data() for memory cleanup

Integration:
- Added serial_uploader to CMakeLists.txt
- Integrated into main loop in basic1.cpp
- Re-scans games after upload to pick up new files

Documentation:
- UPLOAD_TOOL.md: Usage instructions
- sd_card_best_practices.md: Critical lessons learned

Known Issues:
- Game launch after upload occasionally causes freeze (needs investigation)
- Display may not refresh properly after upload

Usage:
  python upload_game.py games/lua_examples/2048.lua /dev/tty.usbmodem101

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-12 22:52:57 -05:00

221 lines
6.9 KiB
C++

// ============================================================================
// 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 <stdio.h>
#include <string.h>
#include <vector>
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<LuaGameFactoryData*> factory_data_list;
void LuaGameLoader::clear_factory_data() {
// Delete all factory data and clear the vector
for (LuaGameFactoryData* data : factory_data_list) {
delete data;
}
factory_data_list.clear();
printf("LuaGameLoader: Cleared all factory data\n");
}
// 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;
}