abse/gameboy-advance

A tscircuit package created by abse.

Version
0.0.45
License
unset
Stars
0

firmware/game-console/main.c

#include "board.h"
#include "ff.h"
#include "hardware/psram.h"
#include "pico/stdlib.h"
#include "sd_card.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void doom_run(const char *path);
static FATFS fs;
static FRESULT mounted;
static int psram_ok;
#define MAX_GAMES 12
static char games[MAX_GAMES][192];
static unsigned game_count, selected;
static int is_wad(const char *name) {
    size_t n = strlen(name);
    return n > 4 && !strcasecmp(name + n - 4, ".wad");
}
static void scan_dir(const char *path, unsigned depth) {
    DIR dir;
    FILINFO info;
    if (f_opendir(&dir, path) != FR_OK)
        return;
    while (game_count < MAX_GAMES && f_readdir(&dir, &info) == FR_OK && info.fname[0]) {
        if (info.fname[0] == '.')
            continue;
        char child[192];
        if (snprintf(child, sizeof(child), "%s/%s", path, info.fname) >= (int)sizeof(child))
            continue;
        if ((info.fattrib & AM_DIR) && depth)
            scan_dir(child, depth - 1);
        else if (!(info.fattrib & AM_DIR) && is_wad(info.fname))
            strcpy(games[game_count++], child);
    }
    f_closedir(&dir);
}
static void scan_games(void) {
    game_count = 0;
    selected = 0;
    if (!mounted)
        scan_dir("/games", 1);
}
static void title_for(const char *path, char *out, size_t n) {
    const char *base = strrchr(path, '/');
    base = base ? base + 1 : path;
    if (!strcasecmp(base, "doom1.wad")) {
        snprintf(out, n, "DOOM SHAREWARE");
        return;
    }
    size_t i = 0;
    while (base[i] && base[i] != '.' && i + 1 < n) {
        out[i] = base[i] == '_' ? ' ' : toupper((unsigned char)base[i]);
        i++;
    }
    out[i] = 0;
}
static void menu(void) {
    lcd_rect(0, 0, 480, 320, 8, 16, 28);
    lcd_text(24, 20, "GAME SELECT", 5);
    lcd_rect(24, 67, 432, 3, 0, 184, 176);
    if (mounted) {
        lcd_text(24, 98, "SD CARD NOT READY", 3);
        char msg[36];
        snprintf(msg, sizeof(msg), "SD ERROR %d", mounted);
        lcd_text(24, 140, msg, 2);
        lcd_text(24, 190, "INSERT CARD AND PRESS B", 2);
    } else if (!game_count) {
        lcd_text(24, 98, "NO GAMES ON SD", 3);
        lcd_text(24, 148, "ADD WAD FILES TO", 3);
        lcd_text(24, 183, "GAMES FOLDER", 3);
    } else {
        unsigned start = selected / 3 * 3;
        for (unsigned i = start; i < game_count && i < start + 3; i++) {
            int y = 86 + (i - start) * 56;
            char title[25];
            title_for(games[i], title, sizeof(title));
            if (i == selected) {
                lcd_rect(16, y, 448, 49, 20, 64, 80);
                lcd_rect(16, y, 4, 49, 0, 220, 180);
            }
            lcd_text(30, y + 15, title, 3);
        }
    }
    lcd_text(24, 245,
             lcd_fast_mode == 2   ? "FAST FULL COLOR - Y SWITCH"
             : lcd_fast_mode == 1 ? "DITHERED TEST MODE"
                                  : "ORIGINAL COLOR - Y FOR SPEED",
             2);
    lcd_text(24, 267, psram_ok ? (lcd_large ? "X FULL SCREEN" : "X SMALL SCREEN") : "RAM ERROR", 2);
    lcd_text(24, 294, "A PLAY  B REFRESH  UP DOWN SELECT", 2);
}
static void mount_card(void) {
    mounted = f_mount(&fs, "0:", 1);
    if (!mounted) {
        f_mkdir("/games");
        f_mkdir("/games/doom");
    }
    scan_games();
}
static void status(void) {
    printf("STATUS sd=%d psram_bytes=%lu psram_ok=%d buttons=%03x games=%u\n", mounted,
           (unsigned long)psram_get_size(), psram_ok, buttons_read(), game_count);
}
static void list(const char *path) {
    DIR d;
    FILINFO f;
    FRESULT r = f_opendir(&d, path);
    printf("LIST %s result=%d\n", path, r);
    if (!r) {
        while (!f_readdir(&d, &f) && f.fname[0])
            printf("%c %lu %s\n", (f.fattrib & AM_DIR) ? 'D' : 'F', (unsigned long)f.fsize,
                   f.fname);
        f_closedir(&d);
    }
    puts("END");
}
static void launch_selected(void) {
    if (mounted || !psram_ok || !game_count) {
        puts("ERROR SD, RAM, or game missing");
        return;
    }
    FIL f;
    uint8_t hdr[12] = {0};
    UINT got;
    FRESULT r = f_open(&f, games[selected], FA_READ);
    if (r) {
        printf("ERROR open=%d\n", r);
        return;
    }
    r = f_read(&f, hdr, sizeof(hdr), &got);
    uint32_t lumps = hdr[4] | hdr[5] << 8 | hdr[6] << 16 | hdr[7] << 24;
    uint32_t directory = hdr[8] | hdr[9] << 8 | hdr[10] << 16 | hdr[11] << 24;
    int valid = !r && got == 12 && !memcmp(hdr, "IWAD", 4) && lumps > 0 && lumps < 100000 &&
                ((uint64_t)directory + lumps * 16 <= f_size(&f));
    f_close(&f);
    if (!valid) {
        puts("ERROR invalid IWAD");
        return;
    }
    while (buttons_read() & ((1 << 4) | (1 << 9)))
        sleep_ms(10);
    doom_run(games[selected]);
}
static uint32_t crc_step(uint32_t crc, const uint8_t *p, size_t n) {
    while (n--) {
        crc ^= *p++;
        for (int k = 0; k < 8; k++)
            crc = (crc >> 1) ^ (0xedb88320u & -(crc & 1));
    }
    return crc;
}
static void upload(char *args) {
    char path[180];
    unsigned long size, expect;
    if (sscanf(args, "%179s %lu %lx", path, &size, &expect) != 3 || strncmp(path, "/games/", 7) ||
        strstr(path, "..") || size > 20000000) {
        puts("ERROR bad upload");
        return;
    }
    char temp[200];
    snprintf(temp, sizeof(temp), "%s.part", path);
    FIL f;
    FRESULT r = f_open(&f, temp, FA_WRITE | FA_CREATE_ALWAYS);
    if (r) {
        printf("ERROR open=%d\n", r);
        return;
    }
    printf("READY %lu\n", size);
    fflush(stdout);
    uint8_t buf[4096];
    uint32_t crc = ~0u;
    unsigned long total = 0;
    while (total < size) {
        unsigned n = size - total > sizeof(buf) ? sizeof(buf) : size - total;
        for (unsigned i = 0; i < n; i++) {
            int c = getchar_timeout_us(5000000);
            if (c < 0) {
                puts("ERROR timeout");
                f_close(&f);
                f_unlink(temp);
                return;
            }
            buf[i] = c;
        }
        UINT wrote;
        r = f_write(&f, buf, n, &wrote);
        if (r || wrote != n) {
            printf("ERROR write=%d\n", r);
            f_close(&f);
            return;
        }
        crc = crc_step(crc, buf, n);
        total += n;
        printf("ACK %lu\n", total);
        fflush(stdout);
    }
    f_sync(&f);
    f_close(&f);
    crc = ~crc;
    if (crc != expect) {
        printf("ERROR crc=%08lx\n", (unsigned long)crc);
        f_unlink(temp);
        return;
    }
    FILINFO info;
    if (f_stat(path, &info) == FR_OK) {
        puts("ERROR target exists");
        return;
    }
    uint32_t readcrc = ~0u;
    UINT got;
    r = f_open(&f, temp, FA_READ);
    if (r) {
        printf("ERROR verify open=%d\n", r);
        return;
    }
    while ((r = f_read(&f, buf, sizeof(buf), &got)) == FR_OK && got)
        readcrc = crc_step(readcrc, buf, got);
    f_close(&f);
    if (r || ~readcrc != expect) {
        puts("ERROR SD readback checksum");
        return;
    }
    r = f_rename(temp, path);
    printf("DONE %d %lu %08lx\n", r, total, (unsigned long)crc);
    scan_games();
    menu();
}

int main(void) {
    stdio_init_all();
    board_init();
    sleep_ms(500);
    size_t size = psram_get_size();
    if (size) {
        volatile uint32_t *p = (volatile uint32_t *)0x11000000;
        psram_ok = 1;
        for (unsigned i = 0; i < 16384; i++)
            p[i] = i ^ 0xa55a1234;
        for (unsigned i = 0; i < 16384; i++)
            if (p[i] != (i ^ 0xa55a1234))
                psram_ok = 0;
    }
    mount_card();
    menu();
    char line[256];
    unsigned n = 0;
    uint32_t last = 0;
    uint16_t prev = buttons_read();
    while (1) {
        int c = getchar_timeout_us(1000);
        if (c >= 0) {
            if (c == '\n' || c == '\r') {
                line[n] = 0;
                if (n) {
                    if (!strcmp(line, "status"))
                        status();
                    else if (!strcmp(line, "retry")) {
                        mount_card();
                        status();
                        menu();
                    } else if (!strcmp(line, "colors")) {
                        lcd_calibration();
                        while (!(buttons_read() & ((1 << 4) | (1 << 9)))) {
                            if (getchar_timeout_us(1000) == 'r')
                                break;
                        }
                        while (buttons_read() & ((1 << 4) | (1 << 9)))
                            sleep_ms(10);
                        menu();
                    } else if (!strncmp(line, "ls ", 3))
                        list(line + 3);
                    else if (!strncmp(line, "put ", 4))
                        upload(line + 4);
                    else if (!strcmp(line, "launch"))
                        launch_selected();
                    else
                        puts("ERROR unknown command");
                }
                n = 0;
            } else if (n < sizeof(line) - 1)
                line[n++] = c;
        }
        uint16_t b = buttons_read(), edge = b & ~prev;
        if (edge & ((1 << 4) | (1 << 9)))
            launch_selected();
        if (edge & (1 << 5)) {
            mount_card();
            menu();
        }
        if (edge & (1 << 7)) {
            lcd_fast_mode = lcd_fast_mode == 0 ? 2 : 0;
            menu();
        }
        if (edge & (1 << 6)) {
            lcd_large = !lcd_large;
            menu();
        }
        if (game_count && (edge & 1)) {
            selected = (selected + game_count - 1) % game_count;
            menu();
        }
        if (game_count && (edge & 2)) {
            selected = (selected + 1) % game_count;
            menu();
        }
        if (b != prev) {
            printf("BUTTONS %03x\n", b);
            prev = b;
        }
        if (to_ms_since_boot(get_absolute_time()) - last > 5000) {
            status();
            last = to_ms_since_boot(get_absolute_time());
        }
    }
}