Initial import: ESP32 ZiFi32 firmware with SMB2/3 VFS bridge

Based on https://github.com/tslabs/zx-evo/tree/master/pentevo/esp32,
extended with an SMB2/3 server that exposes the ZX Evolution's real SD
card as a network share (browse/download/upload/delete), plus assorted
stability and protocol-correctness fixes in vfs_bridge.cpp and the
vendored libsmb2 - see firmware/README.md for the detailed changelog
and current status.

Also adds:
- firmware/: ready-to-flash compiled binaries + flashing instructions
- hardware/: adapter board connection diagram, PCB photo, and Gerber
  files
- src/third_party/libsmb2: vendored from sahlberg/libsmb2 (LGPL-2.1,
  see its own COPYING/LICENCE files), de-submoduled to a plain
  snapshot with local patches applied directly

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 16:00:05 +03:00
co-authored by Claude Sonnet 5
commit 6d835bc44c
377 changed files with 189430 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
file(GLOB SOURCES "*.c" "*.cpp")
idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ".")
idf_build_set_property(COMPILE_OPTIONS "-ggdb;-g3" APPEND)
idf_build_set_property(COMPILE_OPTIONS "-Wno-missing-field-initializers" APPEND)
idf_build_set_property(COMPILE_OPTIONS "-Wno-implicit-fallthrough" APPEND)
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error)
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable)
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function)
+314
View File
@@ -0,0 +1,314 @@
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_tls.h"
#include "esp_crt_bundle.h"
#include "main.h"
#include "at_uart.h"
#include "at_cip.h"
#include "wifi.h"
static const char *TAG = "at_cip";
static int cip_sock = -1;
static int cip_type = CIP_TYPE_NONE;
static char cip_host[64];
static int cip_port = 0;
static esp_tls_t *cip_tls = NULL;
void at_cip_init()
{
cip_sock = -1;
cip_type = CIP_TYPE_NONE;
cip_host[0] = 0;
cip_port = 0;
cip_tls = NULL;
}
bool at_cip_is_connected()
{
return cip_sock >= 0 || cip_tls != NULL;
}
void at_cip_status_line(char *buf, size_t buflen)
{
if (!at_cip_is_connected())
{
snprintf(buf, buflen, "STATUS:0");
return;
}
const char *type_str = cip_type == CIP_TYPE_UDP ? "UDP" : cip_type == CIP_TYPE_SSL ? "SSL" : "TCP";
snprintf(buf, buflen, "STATUS:3\r\n+CIPSTATUS:0,\"%s\",\"%s\",%d,0,0", type_str, cip_host, cip_port);
}
// Resolves and connects a raw socket via the plain BSD gethostbyname()/connect()
// path. Shared by plain TCP and (as of the SSL support below) also by the TLS
// path: esp_tls's own built-in connect step goes through getaddrinfo() instead,
// which crashes (LoadProhibited, NULL FreeRTOS queue handle deep inside lwIP's
// tcpip_task message-passing - confirmed via backtrace against esp-spi.elf,
// root cause not fully understood) the first time it's exercised from this
// task. gethostbyname() uses a different, older lwIP DNS code path that has
// always worked fine here, so SSL connects through it too and only hands
// esp_tls an already-open socket, skipping esp_tls's DNS/connect step entirely.
static int at_cip_connect_raw_tcp(int sock_type, const char *host, int port, bool set_rcv_timeout)
{
// gethostbyname() (and, for the SSL caller, esp_tls's own getaddrinfo()
// fallback below) both post through tcpip_task's mailbox - if this is the
// very first network command since boot (no AT+CWJAP/AT+CWLAP yet), that
// mailbox doesn't exist yet and the post crashes with a NULL-queue-handle
// LoadProhibited (EXCVADDR=0x54, confirmed via console backtrace: reproduced
// with a plain AT+CIPSTART="TCP" to a literal IP right after a fresh boot,
// fixed by ensuring the WiFi stack is up first, same lazy-init used by
// AT+CWLAP - safe to call again if already initialized).
if (!net_mtx)
initialize_wifi();
struct hostent *server = gethostbyname(host);
if (!server)
{
ESP_LOGW(TAG, "DNS lookup failed for '%s'", host);
return -1;
}
int sock = socket(AF_INET, sock_type, 0);
if (sock < 0)
{
ESP_LOGW(TAG, "socket() failed");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);
// A short receive timeout lets at_cip_rx_task poll periodically instead of
// blocking forever, so it notices at_cip_close() promptly. Skipped here for
// the SSL caller: applying it before the TLS handshake made mbedtls's own
// blocking reads see spurious timeouts mid-handshake (a partial/timed-out
// read on a socket mbedtls expects to block on desyncs its record parser -
// confirmed by this exact change turning consistent MBEDTLS_ERR_SSL_INVALID_RECORD
// handshake failures into a clean handshake). The SSL path sets this itself
// after the handshake completes, once the socket moves to steady-state reads.
if (set_rcv_timeout)
{
struct timeval tv = { 0, 200 * 1000 };
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
if (sock_type == SOCK_DGRAM)
return sock; // UDP: no connect() handshake to wait on
// Bound the connect() itself with an explicit timeout via a non-blocking
// connect + select(): an unreachable-but-not-immediately-rejected address
// (e.g. a black-holed IP) can otherwise leave the OS/lwip TCP SYN retry
// logic blocking this call for a minute or more, freezing the whole AT
// command interface for that entire time.
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
int rc = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
if (rc < 0 && errno != EINPROGRESS)
{
ESP_LOGW(TAG, "connect() to %s:%d failed immediately", host, port);
close(sock);
return -1;
}
if (rc < 0) // EINPROGRESS - wait for it to complete
{
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sock, &wfds);
struct timeval conn_tv = { 10, 0 }; // 10s connect timeout
int sel = select(sock + 1, NULL, &wfds, NULL, &conn_tv);
if (sel <= 0)
{
ESP_LOGW(TAG, "connect() to %s:%d timed out", host, port);
close(sock);
return -1;
}
int so_err = 0;
socklen_t so_len = sizeof(so_err);
getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_err, &so_len);
if (so_err != 0)
{
ESP_LOGW(TAG, "connect() to %s:%d failed: errno %d", host, port, so_err);
close(sock);
return -1;
}
}
fcntl(sock, F_SETFL, flags); // back to blocking for the (rare) case send() would need it
return sock;
}
static int at_cip_start_ssl(const char *host, int port)
{
int sock = at_cip_connect_raw_tcp(SOCK_STREAM, host, port, false);
if (sock < 0)
return -1;
// Kept local until the handshake fully succeeds below, deliberately not
// written to the global cip_tls yet: at_cip_is_connected() (checked by
// at_cip_rx_task's poll loop, running concurrently in its own task) goes by
// cip_tls != NULL, and that task calling esp_tls_conn_read() on a context
// that's mid-handshake raced with esp_tls_conn_new_sync() below over the
// same mbedtls ssl_context - confirmed via mbedtls debug log showing
// interleaved flush-output calls and a corrupted ssl->out_left, manifesting
// as MBEDTLS_ERR_SSL_INTERNAL_ERROR then MBEDTLS_ERR_SSL_INVALID_RECORD.
// The plain-TCP path never had this problem because it keeps its socket in
// a local variable the same way, only publishing to cip_sock at the very end.
esp_tls_t *tls = esp_tls_init();
if (!tls)
{
ESP_LOGW(TAG, "esp_tls_init failed");
close(sock);
return -1;
}
esp_tls_set_conn_sockfd(tls, sock);
esp_tls_set_conn_state(tls, ESP_TLS_CONNECTING); // skip esp_tls's own DNS/connect step - see at_cip_connect_raw_tcp()
esp_tls_cfg_t cfg = {};
cfg.crt_bundle_attach = esp_crt_bundle_attach;
cfg.timeout_ms = 10000; // matches the connect timeout already spent above
int rc = esp_tls_conn_new_sync(host, strlen(host), port, &cfg, tls);
if (rc != 1)
{
ESP_LOGW(TAG, "TLS handshake with %s:%d failed", host, port);
esp_tls_conn_destroy(tls); // also closes sock, it now owns the fd
return -1;
}
// Now that the handshake is done, throttle reads the same way the plain-TCP
// path does, so at_cip_rx_task's poll loop notices at_cip_close() promptly.
struct timeval tv = { 0, 200 * 1000 };
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
cip_tls = tls; // now safe to publish: handshake is done, at_cip_rx_task may use it
cip_type = CIP_TYPE_SSL;
strlcpy(cip_host, host, sizeof(cip_host));
cip_port = port;
ESP_LOGI(TAG, "connected SSL %s:%d", host, port);
return 0;
}
int at_cip_start(const char *type, const char *host, int port)
{
if (at_cip_is_connected())
return -2; // already connected
if (!strcasecmp(type, "SSL"))
return at_cip_start_ssl(host, port);
int sock_type = 0;
if (!strcasecmp(type, "TCP"))
sock_type = SOCK_STREAM;
else if (!strcasecmp(type, "UDP"))
sock_type = SOCK_DGRAM;
else
{
ESP_LOGW(TAG, "unknown type '%s'", type);
return -1;
}
int sock = at_cip_connect_raw_tcp(sock_type, host, port, true);
if (sock < 0)
return -1;
cip_sock = sock;
cip_type = (sock_type == SOCK_DGRAM) ? CIP_TYPE_UDP : CIP_TYPE_TCP;
strlcpy(cip_host, host, sizeof(cip_host));
cip_port = port;
ESP_LOGI(TAG, "connected %s %s:%d", cip_type == CIP_TYPE_UDP ? "UDP" : "TCP", host, port);
return 0;
}
int at_cip_send(const uint8_t *data, size_t len)
{
if (cip_tls) return (int)esp_tls_conn_write(cip_tls, data, len);
if (cip_sock < 0) return -1;
return send(cip_sock, data, len, 0);
}
void at_cip_close()
{
if (!at_cip_is_connected()) return;
if (cip_tls)
{
esp_tls_conn_destroy(cip_tls);
cip_tls = NULL;
}
else
{
close(cip_sock);
cip_sock = -1;
}
cip_type = CIP_TYPE_NONE;
cip_host[0] = 0;
cip_port = 0;
}
void at_cip_rx_task(void *arg)
{
uint8_t buf[1024];
while (1)
{
if (!at_cip_is_connected())
{
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
esp_tls_t *tls = cip_tls; // snapshot; at_cip_close() may change these concurrently
int sock = cip_sock;
int n = tls ? (int)esp_tls_conn_read(tls, buf, sizeof(buf)) : recv(sock, buf, sizeof(buf), 0);
if (n > 0)
{
char hdr[24];
snprintf(hdr, sizeof(hdr), "\r\n+IPD,%d:", n);
at_send_str(hdr);
at_send_raw(buf, n);
}
else if (n == 0)
{
// Peer closed (TCP/SSL only - UDP recv() doesn't return 0 on a connected socket).
at_cip_close();
at_send_str("\r\nCLOSED\r\n");
}
// n < 0: either the SO_RCVTIMEO timeout expired (EAGAIN/EWOULDBLOCK, expected -
// esp_tls_conn_read() surfaces the same underlying-socket timeout as a generic
// negative return too, not distinguished from a real error here, matching how
// the plain-TCP path already treats every negative read as "retry") or the
// connection was closed out from under us by at_cip_close() (also fine,
// at_cip_is_connected() will be false at the top next time).
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
enum
{
CIP_TYPE_NONE = 0,
CIP_TYPE_TCP,
CIP_TYPE_UDP,
CIP_TYPE_SSL,
};
void at_cip_init();
int at_cip_start(const char *type, const char *host, int port); // 0 ok, <0 on error, -2 if already connected
int at_cip_send(const uint8_t *data, size_t len); // returns bytes sent, or <0
void at_cip_close();
bool at_cip_is_connected();
void at_cip_status_line(char *buf, size_t buflen);
void at_cip_rx_task(void *arg);
+572
View File
@@ -0,0 +1,572 @@
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "wifi.h"
#include "at_cip.h"
#include "at_uart.h"
#define AT_CIPSEND_MAX_LEN 1460
static const char *TAG = "at_uart";
static bool at_echo = true;
enum
{
AT_MODE_LINE,
AT_MODE_CIPSEND_DATA,
};
static int at_mode = AT_MODE_LINE;
static uint8_t at_cipsend_buf[AT_CIPSEND_MAX_LEN];
static size_t at_cipsend_len = 0;
static size_t at_cipsend_have = 0;
void at_uart_init()
{
uart_config_t cfg = {};
cfg.baud_rate = AT_UART_BAUD;
cfg.data_bits = UART_DATA_8_BITS;
cfg.parity = UART_PARITY_DISABLE;
cfg.stop_bits = UART_STOP_BITS_1;
cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
cfg.source_clk = UART_SCLK_DEFAULT;
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, AT_UART_RX_BUF_SIZE, AT_UART_TX_BUF_SIZE, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &cfg));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, AT_UART_TX_PIN, AT_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_LOGI(TAG, "AT UART ready: TX=%d RX=%d %d 8N1", AT_UART_TX_PIN, AT_UART_RX_PIN, AT_UART_BAUD);
// Real ESP8266 AT firmware emits this on the AT UART itself after boot/AT+RST;
// AT client libraries commonly block waiting for it before sending further commands.
at_send_raw("\r\nready\r\n", 9);
}
void at_send_raw(const void *data, size_t len)
{
uart_write_bytes(UART_NUM_1, (const char*)data, len);
}
void at_send_str(const char *s)
{
at_send_raw(s, strlen(s));
}
static void at_reply_ok()
{
at_send_str("\r\nOK\r\n");
}
static void at_reply_error()
{
at_send_str("\r\nERROR\r\n");
}
static void at_cmd_gmr()
{
// The ZX Evolution client's single-shot display capture after AT+GMR is
// timing-sensitive on real hardware (verified: even small changes in send
// timing shift what fraction of this reply the client's display happens to
// catch, sometimes none of it, without affecting correctness - GMR's actual
// reply content and the OK are always sent and processed correctly
// regardless). A short delay between the echoed command line and the reply
// gives the client's display time to get ready before the content arrives.
vTaskDelay(pdMS_TO_TICKS(500));
// No leading CRLF and no blank line before OK: verified byte-for-byte
// against a real ESP8266 AT module's actual AT+GMR reply on real hardware.
char buf[192];
at_send_str(CP_STRING);
at_send_str("\r\n");
at_send_str(API_STRING);
at_send_str("\r\n");
snprintf(buf, sizeof(buf), "SDK version:%s", IDF_VER);
at_send_str(buf);
at_send_str("\r\n");
at_send_str("OK");
at_send_str("\r\n");
}
// Splits comma-separated args in place, honoring double-quoted tokens
// (e.g. `"ssid","pass"` or `"TCP",host,port`). Returns the number of tokens
// found (up to max_args); each out[i] points into 'args' (NUL-terminated).
static int at_parse_args(char *args, char *out[], int max_args)
{
int count = 0;
char *p = args;
while (*p && count < max_args)
{
while (*p == ' ') p++;
if (!*p) break;
if (*p == '"')
{
p++;
out[count++] = p;
while (*p && *p != '"') p++;
if (*p == '"') *p++ = 0;
}
else
{
out[count++] = p;
while (*p && *p != ',') p++;
}
while (*p == ' ') p++;
if (*p == ',') { *p = 0; p++; }
else if (*p) *p = 0;
}
return count;
}
static void at_cmd_cwmode(char *args, bool is_query)
{
if (is_query)
{
char buf[24];
snprintf(buf, sizeof(buf), "\r\n+CWMODE:1\r\n");
at_send_str(buf);
at_reply_ok();
return;
}
if (!args || strcmp(args, "1"))
{
at_reply_error(); // only STA mode (1) is supported - AP/APSTA out of scope
return;
}
at_reply_ok();
}
static void at_cmd_cwjap(char *args)
{
char *tok[2];
if (!args || at_parse_args(args, tok, 2) != 2)
{
at_reply_error();
return;
}
bool connected = wifi_connect_locked(tok[0], tok[1], CONNECT_TIMEOUT_MS);
if (!connected)
{
at_reply_error();
return;
}
// The ZX Evolution client (zifi_orig.asm) sends every AT command via zifi_send,
// which does a throwaway read (zifi_check_receive_command) right after writing
// the command - it patiently polls (screen-frame-gated, ~5s worst case) until
// ANY byte shows up, then slurps and discards it. For AT+CWJAP specifically,
// a SEPARATE loop right after re-polls for "OK\r" with no timeout at all. Real
// ESP8266 hardware takes several seconds to actually join, so that throwaway
// read naturally times out empty before the real reply lands, leaving it for
// the real wait loop to catch. Our join is near-instant (verified: the discard
// read grabs our whole reply within milliseconds, then the real wait loop hangs
// forever with nothing left to find) - so replying quickly here deterministically
// breaks this client. Deliberately hold the reply past that ~5s throwaway window.
vTaskDelay(pdMS_TO_TICKS(6500));
at_reply_ok();
}
static void at_cmd_cwqap()
{
wifi_disconnect_now_locked();
at_reply_ok();
}
static void at_cmd_cwlap()
{
if (!net_mtx)
initialize_wifi(); // lazily creates net_mtx as its first action
if (xSemaphoreTake(net_mtx, portMAX_DELAY) != pdTRUE)
{
at_reply_error();
return;
}
esp_err_t err = (esp_err_t)wf_scan(300);
if (err != ESP_OK)
{
xSemaphoreGive(net_mtx);
at_reply_error();
return;
}
uint16_t num = wf_get_ap_num();
char line[160];
for (uint16_t i = 0; i < num; i++)
{
uint8_t auth, chan;
int8_t rssi;
uint8_t *ssid;
wf_get_ap(i, auth, rssi, chan, ssid);
snprintf(line, sizeof(line), "\r\n+CWLAP:(%u,\"%s\",%d,%u)", auth, ssid, rssi, chan);
at_send_str(line);
}
xSemaphoreGive(net_mtx);
at_reply_ok();
}
static void at_cmd_cipsntpcfg(char *args, bool is_query)
{
bool enable;
int timezone;
char s1[64], s2[64], s3[64];
if (is_query)
{
sntp_get_config(enable, timezone, s1, s2, s3, sizeof(s1));
char buf[220];
if (s3[0])
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\",\"%s\",\"%s\"\r\n", enable ? 1 : 0, timezone, s1, s2, s3);
else if (s2[0])
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\",\"%s\"\r\n", enable ? 1 : 0, timezone, s1, s2);
else
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\"\r\n", enable ? 1 : 0, timezone, s1);
at_send_str(buf);
at_reply_ok();
return;
}
char *tok[5];
int n = args ? at_parse_args(args, tok, 5) : 0;
if (n < 2)
{
at_reply_error();
return;
}
char *endp = NULL;
long en = strtol(tok[0], &endp, 10);
if (!endp || *endp || (en != 0 && en != 1))
{
at_reply_error();
return;
}
endp = NULL;
long tz = strtol(tok[1], &endp, 10);
if (!endp || *endp || tz < -12 || tz > 14)
{
at_reply_error();
return;
}
sntp_apply_config(en != 0, (int)tz, n >= 3 ? tok[2] : NULL, n >= 4 ? tok[3] : NULL, n >= 5 ? tok[4] : NULL);
at_reply_ok();
}
static void at_cmd_cipsntptime()
{
char timestr[40];
sntp_format_time(timestr, sizeof(timestr));
char buf[64];
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPTIME:%s\r\n", timestr);
at_send_str(buf);
at_reply_ok();
}
static void at_cmd_cifsr()
{
uint8_t ip[4], mask[4], gate[4], mac[6] = {0};
get_ip(ip, mask, gate);
esp_wifi_get_mac(WIFI_IF_STA, mac);
char buf[96];
snprintf(buf, sizeof(buf), "\r\n+CIFSR:STAIP,\"%u.%u.%u.%u\"\r\n+CIFSR:STAMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n",
ip[0], ip[1], ip[2], ip[3], mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
at_send_str(buf);
at_reply_ok();
}
static void at_cmd_cipstatus()
{
char buf[128];
at_cip_status_line(buf, sizeof(buf));
at_send_str("\r\n");
at_send_str(buf);
at_send_str("\r\n");
at_reply_ok();
}
static void at_cmd_cipstart(char *args)
{
char *tok[3];
if (!args || at_parse_args(args, tok, 3) != 3)
{
at_reply_error();
return;
}
int port = atoi(tok[2]);
int rc = at_cip_start(tok[0], tok[1], port);
if (rc == -2)
{
at_send_str("\r\nALREADY CONNECTED\r\n");
at_reply_error();
return;
}
if (rc != 0)
{
at_reply_error();
return;
}
at_send_str("\r\nCONNECT\r\n");
at_reply_ok();
}
static void at_cmd_cipsend(char *args)
{
if (!args)
{
at_reply_error();
return;
}
int len = atoi(args);
if (len <= 0 || len > AT_CIPSEND_MAX_LEN)
{
at_reply_error();
return;
}
if (!at_cip_is_connected())
{
at_reply_error();
return;
}
at_cipsend_len = (size_t)len;
at_cipsend_have = 0;
at_mode = AT_MODE_CIPSEND_DATA;
at_send_str("\r\nOK\r\n>");
}
static void at_cmd_cipclose()
{
at_cip_close();
at_send_str("\r\nCLOSED\r\n");
at_reply_ok();
}
// Dispatches one complete, NUL-terminated line (trailing \r/\n already stripped by caller).
static void at_dispatch_line(char *line, size_t len)
{
if (at_echo)
{
at_send_str(line);
at_send_str("\r\n");
}
if (len == 0) return; // silently ignore stray blank lines, matches real AT modules
if (!strcasecmp(line, "AT"))
{
at_reply_ok();
return;
}
if (!strncasecmp(line, "ATE", 3) && len == 4 && (line[3] == '0' || line[3] == '1'))
{
at_echo = (line[3] == '1');
at_reply_ok();
return;
}
if (strncasecmp(line, "AT+", 3) != 0)
{
at_reply_error();
return;
}
char *cmd = line + 3;
char *eq = strchr(cmd, '=');
char *args = NULL;
bool is_query = false;
if (eq)
{
*eq = 0;
args = eq + 1;
}
else
{
size_t clen = strlen(cmd);
if (clen && cmd[clen - 1] == '?')
{
cmd[clen - 1] = 0;
is_query = true;
}
}
if (!strcasecmp(cmd, "RST"))
{
at_reply_ok();
ESP_LOGE(TAG, "software restart requested by AT+RST on UART1");
vTaskDelay(pdMS_TO_TICKS(50));
esp_restart();
}
else if (!strcasecmp(cmd, "GMR"))
at_cmd_gmr();
else if (!strcasecmp(cmd, "CWMODE") || !strcasecmp(cmd, "CWMODE_CUR") || !strcasecmp(cmd, "CWMODE_DEF"))
at_cmd_cwmode(args, is_query);
else if (!strcasecmp(cmd, "CWJAP") || !strcasecmp(cmd, "CWJAP_CUR") || !strcasecmp(cmd, "CWJAP_DEF"))
at_cmd_cwjap(args);
else if (!strcasecmp(cmd, "CWQAP"))
at_cmd_cwqap();
else if (!strcasecmp(cmd, "CWLAP"))
at_cmd_cwlap();
else if (!strcasecmp(cmd, "CWAUTOCONN"))
at_reply_ok(); // autoconnect-on-boot persistence is out of scope by design; accept and no-op
else if (!strcasecmp(cmd, "CIPMUX"))
{
// Only single-connection mode (0) is supported - CIPMUX multi-connection is out of scope.
if (args && !strcmp(args, "0"))
at_reply_ok();
else
at_reply_error();
}
else if (!strcasecmp(cmd, "CIFSR"))
at_cmd_cifsr();
else if (!strcasecmp(cmd, "CIPSNTPCFG"))
at_cmd_cipsntpcfg(args, is_query);
else if (!strcasecmp(cmd, "CIPSNTPTIME"))
at_cmd_cipsntptime();
else if (!strcasecmp(cmd, "CIPSTATUS"))
at_cmd_cipstatus();
else if (!strcasecmp(cmd, "CIPSTART"))
at_cmd_cipstart(args);
else if (!strcasecmp(cmd, "CIPSEND"))
at_cmd_cipsend(args);
else if (!strcasecmp(cmd, "CIPCLOSE"))
at_cmd_cipclose();
else
{
ESP_LOGW(TAG, "unknown command '%s'", cmd);
at_reply_error();
}
}
static void at_process_cipsend_byte(uint8_t b)
{
at_cipsend_buf[at_cipsend_have++] = b;
if (at_cipsend_have < at_cipsend_len)
return;
int sent = at_cip_send(at_cipsend_buf, at_cipsend_have);
at_mode = AT_MODE_LINE;
if (sent < 0 || (size_t)sent != at_cipsend_have)
at_send_str("\r\nSEND FAIL\r\n");
else
at_send_str("\r\nSEND OK\r\n");
}
void at_task(void *arg)
{
char linebuf[AT_LINE_MAX_LEN];
size_t linelen = 0;
bool just_saw_cr = false;
uint8_t rxbuf[64];
while (1)
{
int n = uart_read_bytes(UART_NUM_1, rxbuf, sizeof(rxbuf), pdMS_TO_TICKS(20));
for (int i = 0; i < n; i++)
{
uint8_t b = rxbuf[i];
// Swallow the LF half of a CRLF pair unconditionally, *before* looking at
// at_mode - a command dispatched on '\r' (e.g. AT+CIPSEND=<len>) can switch
// straight into AT_MODE_CIPSEND_DATA, and the following '\n' must not be
// mistaken for the first byte of that payload.
if (just_saw_cr)
{
just_saw_cr = false;
if (b == '\n') continue;
}
if (at_mode == AT_MODE_CIPSEND_DATA)
{
at_process_cipsend_byte(b);
continue;
}
char c = (char)b;
if (c == '\r')
{
linebuf[linelen] = 0;
at_dispatch_line(linebuf, linelen);
linelen = 0;
just_saw_cr = true;
}
else if (c == '\n')
{
linebuf[linelen] = 0;
at_dispatch_line(linebuf, linelen);
linelen = 0;
}
else if (linelen < AT_LINE_MAX_LEN - 1)
{
linebuf[linelen++] = c;
}
else
{
linelen = 0;
at_reply_error();
}
}
}
}
TaskHandle_t g_at_task_handle = NULL;
TaskHandle_t g_at_cip_rx_task_handle = NULL;
void at_uart_suspend()
{
if (g_at_task_handle) vTaskSuspend(g_at_task_handle);
if (g_at_cip_rx_task_handle) vTaskSuspend(g_at_cip_rx_task_handle);
}
void at_uart_resume()
{
if (g_at_task_handle) vTaskResume(g_at_task_handle);
if (g_at_cip_rx_task_handle) vTaskResume(g_at_cip_rx_task_handle);
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <stddef.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define AT_UART_TX_PIN 18
#define AT_UART_RX_PIN 8
#define AT_UART_BAUD 115200
#define AT_UART_RX_BUF_SIZE 2048
#define AT_UART_TX_BUF_SIZE 2048
#define AT_LINE_MAX_LEN 512
void at_uart_init();
void at_task(void *arg);
void at_send_raw(const void *data, size_t len);
void at_send_str(const char *s);
// Populated by main.cpp's xTaskCreatePinnedToCoreWithCaps() calls for
// at_task/at_cip_rx_task - used by at_uart_suspend()/at_uart_resume() below.
extern TaskHandle_t g_at_task_handle;
extern TaskHandle_t g_at_cip_rx_task_handle;
// Pause/resume the whole AT-command modem interface (both the UART command
// parser and the CIP data-mode RX relay) - used while the SMB2/3 server is
// active, since the Z80 side is single-tasking and can't be driving both
// the classic ZiFi AT interface and the smbvfs.wmf plugin at once anyway.
void at_uart_suspend();
void at_uart_resume();
+10835
View File
File diff suppressed because it is too large Load Diff
+696
View File
@@ -0,0 +1,696 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CmdApp CmdApp;
typedef struct CmdFsManager CmdFsManager;
typedef struct CmdPanel CmdPanel;
void cmd_console_register_system_commands();
void cmd_set_status(CmdApp *app, const char *fmt, ...);
void cmd_show_error(CmdApp *app, const char *title, esp_err_t err);
#ifdef __cplusplus
}
#endif
// --------------- cmd_types.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_TEXT_COLS 80
#define CMD_TEXT_ROWS 30
#define CMD_FILE_NAME_MAX 256
#define CMD_PATH_MAX 256
#define CMD_PANEL_COUNT 2
#ifndef CMD_ERR_BASE
#define CMD_ERR_BASE 0x7000
#endif
typedef esp_err_t CmdErr;
typedef enum
{
CMD_DEVICE_SD = 0,
CMD_DEVICE_FAT,
CMD_DEVICE_TSF,
CMD_DEVICE_MAX,
CMD_DEVICE_INVALID = -1
} cmd_device_id_t;
typedef enum
{
CMD_ENTRY_UNKNOWN = 0,
CMD_ENTRY_FILE,
CMD_ENTRY_DIR,
CMD_ENTRY_PARENT
} cmd_entry_type_t;
typedef enum
{
CMD_ENTRY_FLAG_NONE = 0,
CMD_ENTRY_FLAG_SELECTED = 1u << 0,
CMD_ENTRY_FLAG_ERROR = 1u << 1
} cmd_entry_flags_t;
typedef struct
{
char name[CMD_FILE_NAME_MAX];
cmd_entry_type_t type;
uint64_t size;
uint64_t mtime;
uint32_t flags;
} cmd_file_entry_t;
typedef struct
{
int x;
int y;
} cmd_point_t;
typedef struct
{
int w;
int h;
} cmd_size_t;
typedef struct
{
int x;
int y;
int w;
int h;
} cmd_rect_t;
typedef enum
{
CMD_KEY_NONE = 0,
CMD_KEY_CHAR,
CMD_KEY_ESC,
CMD_KEY_ENTER,
CMD_KEY_BACKSPACE,
CMD_KEY_TAB,
CMD_KEY_UP,
CMD_KEY_DOWN,
CMD_KEY_LEFT,
CMD_KEY_RIGHT,
CMD_KEY_HOME,
CMD_KEY_END,
CMD_KEY_PAGE_UP,
CMD_KEY_PAGE_DOWN,
CMD_KEY_INSERT,
CMD_KEY_DELETE,
CMD_KEY_F1,
CMD_KEY_F2,
CMD_KEY_F3,
CMD_KEY_F4,
CMD_KEY_F5,
CMD_KEY_F6,
CMD_KEY_F7,
CMD_KEY_F8,
CMD_KEY_F9,
CMD_KEY_F10,
CMD_KEY_F11,
CMD_KEY_F12,
CMD_KEY_UNKNOWN
} cmd_key_t;
typedef enum
{
CMD_EVENT_NONE = 0,
CMD_EVENT_KEY,
CMD_EVENT_QUIT
} cmd_event_type_t;
typedef struct
{
cmd_event_type_t type;
cmd_key_t key;
uint32_t ch;
uint32_t mods;
} cmd_event_t;
typedef enum
{
CMD_KEY_MOD_NONE = 0,
CMD_KEY_MOD_SHIFT = 1u << 0,
CMD_KEY_MOD_CTRL = 1u << 1,
CMD_KEY_MOD_ALT = 1u << 2
} cmd_key_mod_t;
typedef enum
{
CMD_OK = ESP_OK,
CMD_ERR_INVALID_DEVICE = CMD_ERR_BASE,
CMD_ERR_NOT_MOUNTED,
CMD_ERR_PATH_TOO_LONG,
CMD_ERR_ENTRY_TOO_LONG,
CMD_ERR_DIR_FULL,
CMD_ERR_INPUT_UNAVAILABLE,
CMD_ERR_CANCELLED
} cmd_err_t;
const char *cmd_device_name(cmd_device_id_t id);
const char *cmd_entry_type_name(cmd_entry_type_t type);
#ifdef __cplusplus
}
#endif
// --------------- cmd_display.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_DISPLAY_DEFAULT_COLS CMD_TEXT_COLS
#define CMD_DISPLAY_DEFAULT_ROWS CMD_TEXT_ROWS
#define CMD_DISPLAY_MAX_COLS 160
#define CMD_DISPLAY_MAX_ROWS 64
#define CMD_DISPLAY_COLS CMD_DISPLAY_MAX_COLS
#define CMD_DISPLAY_ROWS CMD_DISPLAY_MAX_ROWS
#define CMD_DISPLAY_CELL_COUNT (CMD_DISPLAY_COLS * CMD_DISPLAY_ROWS)
#define CMD_DISPLAY_MUX_MAX_BACKENDS 4
#define CMD_DISPLAY_BACKEND_FLAG_FORCE_PARTIAL 1u
typedef uint16_t cmd_display_attr_t;
typedef enum
{
CMD_DISPLAY_COLOR_BLACK = 0,
CMD_DISPLAY_COLOR_BLUE,
CMD_DISPLAY_COLOR_GREEN,
CMD_DISPLAY_COLOR_CYAN,
CMD_DISPLAY_COLOR_RED,
CMD_DISPLAY_COLOR_MAGENTA,
CMD_DISPLAY_COLOR_YELLOW,
CMD_DISPLAY_COLOR_WHITE,
CMD_DISPLAY_COLOR_BRIGHT_BLACK,
CMD_DISPLAY_COLOR_BRIGHT_BLUE,
CMD_DISPLAY_COLOR_BRIGHT_GREEN,
CMD_DISPLAY_COLOR_BRIGHT_CYAN,
CMD_DISPLAY_COLOR_BRIGHT_RED,
CMD_DISPLAY_COLOR_BRIGHT_MAGENTA,
CMD_DISPLAY_COLOR_BRIGHT_YELLOW,
CMD_DISPLAY_COLOR_BRIGHT_WHITE
} cmd_display_color_t;
typedef enum
{
CMD_DISPLAY_ATTR_FLAG_NONE = 0,
CMD_DISPLAY_ATTR_FLAG_BOLD = 1u << 0,
CMD_DISPLAY_ATTR_FLAG_INVERSE = 1u << 1,
CMD_DISPLAY_ATTR_FLAG_UNDERLINE = 1u << 2,
CMD_DISPLAY_ATTR_FLAG_SELECTED = 1u << 3
} cmd_display_attr_flag_t;
typedef enum
{
CMD_DISPLAY_MODE_CONSOLE = 0,
CMD_DISPLAY_MODE_FT812,
CMD_DISPLAY_MODE_MIRROR
} CmdDisplayMode;
#define CMD_DISPLAY_ATTR_FG_MASK 0x000f
#define CMD_DISPLAY_ATTR_BG_MASK 0x00f0
#define CMD_DISPLAY_ATTR_FLAGS_MASK 0xff00
#define CMD_DISPLAY_ATTR_BG_SHIFT 4
#define CMD_DISPLAY_ATTR_FLAGS_SHIFT 8
#define CMD_DISPLAY_ATTR_DEFAULT ((cmd_display_attr_t)(CMD_DISPLAY_COLOR_WHITE | (CMD_DISPLAY_COLOR_BLACK << CMD_DISPLAY_ATTR_BG_SHIFT)))
#define CMD_DISPLAY_BOX_TL ((char)0xC9)
#define CMD_DISPLAY_BOX_TR ((char)0xBB)
#define CMD_DISPLAY_BOX_BL ((char)0xC8)
#define CMD_DISPLAY_BOX_BR ((char)0xBC)
#define CMD_DISPLAY_BOX_H ((char)0xCD)
#define CMD_DISPLAY_BOX_V ((char)0xBA)
typedef struct
{
char ch;
cmd_display_attr_t attr;
} CmdDisplayCell;
typedef struct
{
cmd_size_t size;
CmdDisplayCell cells[CMD_DISPLAY_ROWS][CMD_DISPLAY_COLS];
} CmdDisplayBuffer;
typedef struct CmdDisplayBackend CmdDisplayBackend;
typedef struct CmdDisplayMux CmdDisplayMux;
typedef esp_err_t (*CmdDisplayBackendInitFn)(CmdDisplayBackend *backend);
typedef void (*CmdDisplayBackendDeinitFn)(CmdDisplayBackend *backend);
typedef esp_err_t (*CmdDisplayBackendClearFn)(CmdDisplayBackend *backend);
typedef esp_err_t (*CmdDisplayBackendPresentFn)(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
typedef esp_err_t (*CmdDisplayBackendPutCharFn)(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
typedef esp_err_t (*CmdDisplayBackendWriteTextFn)(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
typedef esp_err_t (*CmdDisplayBackendGetSizeFn)(CmdDisplayBackend *backend, cmd_size_t *size);
struct CmdDisplayBackend
{
const char *name;
void *ctx;
uint32_t flags;
CmdDisplayBackendInitFn init;
CmdDisplayBackendDeinitFn deinit;
CmdDisplayBackendClearFn clear;
CmdDisplayBackendPresentFn present;
CmdDisplayBackendPutCharFn put_char;
CmdDisplayBackendWriteTextFn write_text;
CmdDisplayBackendGetSizeFn get_size;
};
struct CmdDisplayMux
{
CmdDisplayBackend *backends[CMD_DISPLAY_MUX_MAX_BACKENDS];
size_t count;
};
extern CmdDisplayBackend cmd_display_console_backend;
extern CmdDisplayBackend cmd_display_ft812_backend;
cmd_display_attr_t cmd_display_make_attr(cmd_display_color_t fg, cmd_display_color_t bg, uint8_t flags);
cmd_display_color_t cmd_display_attr_fg(cmd_display_attr_t attr);
cmd_display_color_t cmd_display_attr_bg(cmd_display_attr_t attr);
uint8_t cmd_display_attr_flags(cmd_display_attr_t attr);
const char *cmd_display_mode_name(CmdDisplayMode mode);
bool cmd_display_mode_parse(const char *text, CmdDisplayMode *mode);
bool cmd_display_mode_has_console(CmdDisplayMode mode);
bool cmd_display_mode_has_ft812(CmdDisplayMode mode);
void cmd_display_buffer_init(CmdDisplayBuffer *buffer);
esp_err_t cmd_display_buffer_set_size(CmdDisplayBuffer *buffer, int w, int h);
void cmd_display_buffer_clear(CmdDisplayBuffer *buffer, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_put_char(CmdDisplayBuffer *buffer, int x, int y, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_write_text(CmdDisplayBuffer *buffer, int x, int y, const char *text, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_fill_rect(CmdDisplayBuffer *buffer, const cmd_rect_t *rect, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_draw_hline(CmdDisplayBuffer *buffer, int x, int y, int w, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_draw_vline(CmdDisplayBuffer *buffer, int x, int y, int h, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_buffer_draw_box(CmdDisplayBuffer *buffer, const cmd_rect_t *rect, cmd_display_attr_t attr);
CmdDisplayCell *cmd_display_buffer_cell(CmdDisplayBuffer *buffer, int x, int y);
const CmdDisplayCell *cmd_display_buffer_cell_const(const CmdDisplayBuffer *buffer, int x, int y);
bool cmd_display_point_inside(int x, int y);
bool cmd_display_buffer_point_inside(const CmdDisplayBuffer *buffer, int x, int y);
esp_err_t cmd_display_backend_init(CmdDisplayBackend *backend);
void cmd_display_backend_deinit(CmdDisplayBackend *backend);
esp_err_t cmd_display_backend_clear(CmdDisplayBackend *backend);
esp_err_t cmd_display_backend_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
esp_err_t cmd_display_backend_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_backend_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
esp_err_t cmd_display_backend_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
void cmd_display_mux_init(CmdDisplayMux *mux);
esp_err_t cmd_display_mux_add_backend(CmdDisplayMux *mux, CmdDisplayBackend *backend);
esp_err_t cmd_display_mux_add_mode(CmdDisplayMux *mux, CmdDisplayMode mode);
esp_err_t cmd_display_mux_remove_backend(CmdDisplayMux *mux, CmdDisplayBackend *backend);
esp_err_t cmd_display_mux_init_backends(CmdDisplayMux *mux);
esp_err_t cmd_display_mux_get_size(CmdDisplayMux *mux, cmd_size_t *size);
void cmd_display_mux_deinit_backends(CmdDisplayMux *mux);
esp_err_t cmd_display_mux_clear(CmdDisplayMux *mux);
esp_err_t cmd_display_mux_present(CmdDisplayMux *mux, const CmdDisplayBuffer *buffer);
esp_err_t cmd_display_mux_put_char(CmdDisplayMux *mux, int x, int y, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_mux_write_text(CmdDisplayMux *mux, int x, int y, const char *text, cmd_display_attr_t attr);
esp_err_t cmd_display_console_init(CmdDisplayBackend *backend);
void cmd_display_console_deinit(CmdDisplayBackend *backend);
esp_err_t cmd_display_console_clear(CmdDisplayBackend *backend);
esp_err_t cmd_display_console_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
esp_err_t cmd_display_console_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_console_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
esp_err_t cmd_display_console_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
esp_err_t cmd_display_ft812_begin(CmdDisplayBackend *backend);
void cmd_display_ft812_end(CmdDisplayBackend *backend);
esp_err_t cmd_display_ft812_clear(CmdDisplayBackend *backend);
esp_err_t cmd_display_ft812_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
esp_err_t cmd_display_ft812_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
esp_err_t cmd_display_ft812_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
esp_err_t cmd_display_ft812_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
#ifdef __cplusplus
}
#endif
// --------------- cmd_fileops.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CMD_FILEOPS_BUFFER_SIZE
#define CMD_FILEOPS_BUFFER_SIZE 4096
#endif
#ifndef CMD_FILEOPS_MAX_DEPTH
#define CMD_FILEOPS_MAX_DEPTH 16
#endif
typedef enum
{
CMD_FILEOPS_OP_COPY = 0,
CMD_FILEOPS_OP_MOVE,
CMD_FILEOPS_OP_DELETE
} cmd_fileops_op_t;
typedef enum
{
CMD_FILEOPS_FLAG_NONE = 0,
CMD_FILEOPS_FLAG_RECURSIVE = 1u << 0,
CMD_FILEOPS_FLAG_OVERWRITE = 1u << 1
} cmd_fileops_flags_t;
typedef struct
{
cmd_fileops_op_t op;
const char *filename;
uint64_t bytes_done;
uint64_t bytes_total;
size_t files_done;
size_t files_total;
bool can_cancel;
} CmdFileopsProgress;
typedef bool (*CmdFileopsProgressFn)(const CmdFileopsProgress *progress, void *ctx);
typedef struct
{
uint32_t flags;
CmdFileopsProgressFn progress;
void *progress_ctx;
} CmdFileopsOptions;
esp_err_t cmd_fileops_alloc_global_buffers();
void cmd_fileops_free_global_buffers();
esp_err_t cmd_fileops_copy_path(CmdFsManager *fs,
cmd_device_id_t src_device,
const char *src_path,
cmd_device_id_t dst_device,
const char *dst_path,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_copy_file(CmdFsManager *fs,
cmd_device_id_t src_device,
const char *src_path,
cmd_device_id_t dst_device,
const char *dst_path,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_move_path(CmdFsManager *fs,
cmd_device_id_t src_device,
const char *src_path,
cmd_device_id_t dst_device,
const char *dst_path,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_delete_path(CmdFsManager *fs,
cmd_device_id_t device_id,
const char *path,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_copy_selected(CmdPanel *active,
CmdPanel *passive,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_move_selected(CmdPanel *active,
CmdPanel *passive,
const CmdFileopsOptions *options);
esp_err_t cmd_fileops_delete_selected(CmdPanel *panel,
const CmdFileopsOptions *options);
#ifdef __cplusplus
}
#endif
// --------------- cmd_fs.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_FS_DRIVER_MAX CMD_DEVICE_MAX
#ifndef CMD_FS_SD_ROOT_PATH
#define CMD_FS_SD_ROOT_PATH "/sd"
#endif
typedef struct CmdFsDriver CmdFsDriver;
typedef struct CmdFsManager CmdFsManager;
typedef struct CmdFsList CmdFsList;
typedef struct CmdFsFile CmdFsFile;
typedef esp_err_t (*CmdFsMountFn)(CmdFsDriver *driver);
typedef esp_err_t (*CmdFsUnmountFn)(CmdFsDriver *driver);
typedef esp_err_t (*CmdFsListBeginFn)(CmdFsDriver *driver, const char *path, CmdFsList *list);
typedef esp_err_t (*CmdFsListNextFn)(CmdFsDriver *driver, CmdFsList *list, cmd_file_entry_t *entry);
typedef void (*CmdFsListCloseFn)(CmdFsDriver *driver, CmdFsList *list);
typedef esp_err_t (*CmdFsStatFn)(CmdFsDriver *driver, const char *path, cmd_file_entry_t *entry);
typedef esp_err_t (*CmdFsMkdirFn)(CmdFsDriver *driver, const char *path);
typedef esp_err_t (*CmdFsRemoveFn)(CmdFsDriver *driver, const char *path);
typedef esp_err_t (*CmdFsRenameFn)(CmdFsDriver *driver, const char *old_path, const char *new_path);
typedef esp_err_t (*CmdFsOpenFn)(CmdFsDriver *driver, const char *path, CmdFsFile *file);
typedef esp_err_t (*CmdFsReadFn)(CmdFsDriver *driver, CmdFsFile *file, void *dst, size_t size, size_t *out_size);
typedef esp_err_t (*CmdFsWriteFn)(CmdFsDriver *driver, CmdFsFile *file, const void *src, size_t size, size_t *out_size);
typedef esp_err_t (*CmdFsSeekFn)(CmdFsDriver *driver, CmdFsFile *file, int64_t offset, int whence);
typedef esp_err_t (*CmdFsTellFn)(CmdFsDriver *driver, CmdFsFile *file, uint64_t *out_pos);
typedef esp_err_t (*CmdFsCloseFn)(CmdFsDriver *driver, CmdFsFile *file);
struct CmdFsList
{
cmd_device_id_t device_id;
char path[CMD_PATH_MAX];
char abs_path[CMD_PATH_MAX];
void *dir;
size_t index;
bool parent_pending;
uint32_t flags;
};
struct CmdFsFile
{
cmd_device_id_t device_id;
FILE *fp;
void *ctx;
char name[CMD_FILE_NAME_MAX];
uint32_t flags;
};
typedef struct
{
uint64_t total_bytes;
uint64_t free_bytes;
}
CmdFsSpaceInfo;
struct CmdFsDriver
{
cmd_device_id_t device_id;
const char *name;
const char *root_path;
void *ctx;
CmdFsMountFn mount;
CmdFsUnmountFn unmount;
CmdFsListBeginFn list_begin;
CmdFsListNextFn list_next;
CmdFsListCloseFn list_close;
CmdFsStatFn stat;
CmdFsMkdirFn mkdir;
CmdFsRemoveFn remove;
CmdFsRenameFn rename;
CmdFsOpenFn open_read;
CmdFsOpenFn open_write;
CmdFsReadFn read;
CmdFsWriteFn write;
CmdFsSeekFn seek;
CmdFsTellFn tell;
CmdFsCloseFn close;
};
struct CmdFsManager
{
CmdFsDriver *drivers[CMD_FS_DRIVER_MAX];
uint32_t registered_mask;
uint32_t mounted_mask;
};
extern CmdFsDriver cmd_fs_sd_driver;
extern CmdFsDriver cmd_fs_fat_driver;
extern CmdFsDriver cmd_fs_tsf_driver;
bool cmd_fs_is_valid_device_id(cmd_device_id_t device_id);
void cmd_fs_manager_init(CmdFsManager *manager);
esp_err_t cmd_fs_manager_register_driver(CmdFsManager *manager, CmdFsDriver *driver);
esp_err_t cmd_fs_manager_unregister_driver(CmdFsManager *manager, cmd_device_id_t device_id);
CmdFsDriver *cmd_fs_manager_get_driver(CmdFsManager *manager, cmd_device_id_t device_id);
const CmdFsDriver *cmd_fs_manager_get_driver_const(const CmdFsManager *manager, cmd_device_id_t device_id);
esp_err_t cmd_fs_manager_register_default_drivers(CmdFsManager *manager);
esp_err_t cmd_fs_manager_build_abs_path(const CmdFsManager *manager, cmd_device_id_t device_id, const char *current_path, const char *filename, char *out, size_t out_size);
esp_err_t cmd_fs_manager_mount(CmdFsManager *manager, cmd_device_id_t device_id);
esp_err_t cmd_fs_manager_unmount(CmdFsManager *manager, cmd_device_id_t device_id);
esp_err_t cmd_fs_manager_unmount_all(CmdFsManager *manager);
esp_err_t cmd_fs_manager_list_begin(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsList *list);
esp_err_t cmd_fs_manager_list_next(CmdFsManager *manager, CmdFsList *list, cmd_file_entry_t *entry);
void cmd_fs_manager_list_close(CmdFsManager *manager, CmdFsList *list);
esp_err_t cmd_fs_manager_stat(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, cmd_file_entry_t *entry);
esp_err_t cmd_fs_manager_space_info(CmdFsManager *manager, cmd_device_id_t device_id, CmdFsSpaceInfo *info);
esp_err_t cmd_fs_manager_mkdir(CmdFsManager *manager, cmd_device_id_t device_id, const char *path);
esp_err_t cmd_fs_manager_remove(CmdFsManager *manager, cmd_device_id_t device_id, const char *path);
esp_err_t cmd_fs_manager_rename(CmdFsManager *manager, cmd_device_id_t device_id, const char *old_path, const char *new_path);
esp_err_t cmd_fs_manager_open_read(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsFile *file);
esp_err_t cmd_fs_manager_open_write(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsFile *file);
esp_err_t cmd_fs_manager_read(CmdFsManager *manager, CmdFsFile *file, void *dst, size_t size, size_t *out_size);
esp_err_t cmd_fs_manager_write(CmdFsManager *manager, CmdFsFile *file, const void *src, size_t size, size_t *out_size);
esp_err_t cmd_fs_manager_seek(CmdFsManager *manager, CmdFsFile *file, int64_t offset, int whence);
esp_err_t cmd_fs_manager_tell(CmdFsManager *manager, CmdFsFile *file, uint64_t *out_pos);
esp_err_t cmd_fs_manager_close(CmdFsManager *manager, CmdFsFile *file);
esp_err_t cmd_fs_copy_path(const char *src, char *out, size_t out_size);
esp_err_t cmd_fs_join_path(const char *left, const char *right, char *out, size_t out_size);
esp_err_t cmd_fs_join3_path(const char *left, const char *middle, const char *right, char *out, size_t out_size);
esp_err_t cmd_fs_parent_path(const char *path, char *out, size_t out_size);
esp_err_t cmd_fs_extract_filename(const char *path, char *out, size_t out_size);
const char *cmd_fs_filename_ptr(const char *path);
bool cmd_fs_path_is_root(const char *path);
#ifdef __cplusplus
}
#endif
// --------------- cmd_input.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#define CMD_INPUT_WAIT_FOREVER UINT32_MAX
#define CMD_INPUT_ESC_TIMEOUT_MS 120
typedef struct CmdInputBackend CmdInputBackend;
typedef esp_err_t (*cmd_input_begin_fn)(CmdInputBackend *backend);
typedef void (*cmd_input_end_fn)(CmdInputBackend *backend);
typedef esp_err_t (*cmd_input_poll_event_fn)(CmdInputBackend *backend, cmd_event_t *event, uint32_t timeout_ms);
struct CmdInputBackend
{
const char *name;
void *ctx;
cmd_input_begin_fn begin;
cmd_input_end_fn end;
cmd_input_poll_event_fn poll_event;
};
void cmd_input_event_clear(cmd_event_t *event);
void cmd_input_event_key(cmd_event_t *event, cmd_key_t key, uint32_t mods);
void cmd_input_event_char(cmd_event_t *event, uint32_t ch, uint32_t mods);
void cmd_input_event_quit(cmd_event_t *event);
esp_err_t cmd_input_begin(CmdInputBackend *backend);
void cmd_input_end(CmdInputBackend *backend);
esp_err_t cmd_input_poll_event(CmdInputBackend *backend, cmd_event_t *event, uint32_t timeout_ms);
CmdInputBackend *cmd_input_console_backend();
CmdInputBackend *cmd_input_default_backend();
const char *cmd_input_key_name(cmd_key_t key);
#ifdef __cplusplus
}
#endif
// --------------- cmd_panel.h ---------------
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CMD_PANEL_MAX_ENTRIES
#define CMD_PANEL_MAX_ENTRIES 1000
#endif
#ifndef CMD_PANEL_DEFAULT_PAGE_STEP
#define CMD_PANEL_DEFAULT_PAGE_STEP 10
#endif
typedef enum
{
CMD_PANEL_FLAG_NONE = 0,
CMD_PANEL_FLAG_ACTIVE = 1u << 0,
CMD_PANEL_FLAG_ERROR = 1u << 1,
CMD_PANEL_FLAG_TRUNCATED = 1u << 2
} cmd_panel_flags_t;
struct CmdPanel
{
CmdFsManager *fs;
cmd_device_id_t device_id;
char current_path[CMD_PATH_MAX];
cmd_file_entry_t *entries;
size_t capacity;
size_t count;
size_t cursor;
size_t scroll;
size_t view_rows;
size_t selected_count;
uint64_t selected_size;
CmdFsSpaceInfo space_info;
esp_err_t space_info_error;
bool space_info_valid;
bool active;
uint32_t flags;
esp_err_t last_error;
};
esp_err_t cmd_panel_alloc_global_buffers();
void cmd_panel_free_global_buffers();
esp_err_t cmd_panel_alloc_entries(CmdPanel *panel, size_t capacity);
void cmd_panel_free_entries(CmdPanel *panel);
void cmd_panel_init(CmdPanel *panel, CmdFsManager *fs, cmd_device_id_t device_id);
void cmd_panel_reset(CmdPanel *panel);
void cmd_panel_set_active(CmdPanel *panel, bool active);
bool cmd_panel_is_active(const CmdPanel *panel);
esp_err_t cmd_panel_set_path(CmdPanel *panel, const char *path);
esp_err_t cmd_panel_reload(CmdPanel *panel);
esp_err_t cmd_panel_draw(CmdPanel *panel, CmdDisplayBuffer *buffer, const cmd_rect_t *rect);
void cmd_panel_cursor_up(CmdPanel *panel);
void cmd_panel_cursor_down(CmdPanel *panel);
void cmd_panel_cursor_page_up(CmdPanel *panel);
void cmd_panel_cursor_page_down(CmdPanel *panel);
void cmd_panel_cursor_home(CmdPanel *panel);
void cmd_panel_cursor_end(CmdPanel *panel);
const cmd_file_entry_t *cmd_panel_get_selected_entry(const CmdPanel *panel);
void cmd_panel_toggle_selection(CmdPanel *panel);
void cmd_panel_invert_selection(CmdPanel *panel);
void cmd_panel_clear_selection(CmdPanel *panel);
void cmd_panel_update_selection_stats(CmdPanel *panel);
bool cmd_panel_has_selection(const CmdPanel *panel);
esp_err_t cmd_panel_change_device(CmdPanel *panel, cmd_device_id_t device_id);
esp_err_t cmd_panel_enter_selected_dir(CmdPanel *panel);
esp_err_t cmd_panel_go_parent(CmdPanel *panel);
void cmd_panel_clamp_cursor(CmdPanel *panel);
void cmd_panel_ensure_cursor_visible(CmdPanel *panel);
#ifdef __cplusplus
}
#endif
+179
View File
@@ -0,0 +1,179 @@
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_console.h"
#include "esp_log.h"
#include "esp_vfs_dev.h" // Old headers for the aliasing functions
#include "driver/uart_vfs.h"
#include "driver/uart.h"
#include "argtable3/argtable3.h"
#include "linenoise/linenoise.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "main.h"
#include "ft8xx.h"
#include "console.h"
#include "tracker.h"
#include "sfx.h"
#include "tsf.h"
#include "opl.h"
#include "fatfs.h"
#include "cmd.h"
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
#include "wifi.h"
#endif
#if SOC_SDMMC_HOST_SUPPORTED
#include "sdmmc.h"
#endif
#define PROMPT_STR "zifi32"
#define CONSOLE_UART_RX_BUF_SIZE 1024
#define CONSOLE_PROMPT_MAX_LEN 32
char prompt_buf[CONSOLE_PROMPT_MAX_LEN];
const char *prompt = prompt_buf;
void console_register_commands()
{
esp_console_register_help_command();
esp_console_register_system_commands();
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
esp_console_register_wifi_commands();
#endif
ft_console_register_system_commands();
#if SOC_SDMMC_HOST_SUPPORTED
sdmmc_console_register_system_commands();
#endif
xm_console_register_system_commands();
sfx_console_register_system_commands();
tsf_console_register_system_commands();
opl_console_register_system_commands();
fat_console_register_system_commands();
cmd_console_register_system_commands();
}
void console_update_prompt()
{
if (linenoiseIsDumbMode())
{
snprintf(prompt_buf, sizeof(prompt_buf), PROMPT_STR "> ");
return;
}
#if CONFIG_LOG_COLORS
snprintf(prompt_buf, sizeof(prompt_buf), LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR);
#else
snprintf(prompt_buf, sizeof(prompt_buf), PROMPT_STR "> ");
#endif
}
void initialize_console()
{
// Drain stdout before reconfiguring it
fflush(stdout);
fsync(fileno(stdout));
// Disable buffering on stdin
setvbuf(stdin, NULL, _IONBF, 0);
log_sram_used(__FILE_NAME__ ": setvbuf");
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
// Install UART driver for interrupt-driven reads and writes
ESP_ERROR_CHECK(uart_driver_install((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM, CONSOLE_UART_RX_BUF_SIZE, 0, 0, NULL, 0));
log_sram_used(__FILE_NAME__ ": uart_driver_install");
// Tell VFS to use UART driver
uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
// Initialize the console
esp_console_config_t console_config =
{
.max_cmdline_length = 256,
.max_cmdline_args = 128,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
// Configure linenoise line completion library.
// Single-line mode sends less data on each key press and is much more stable on UART.
linenoiseSetMultiLine(0);
// Detect whether the terminal supports escape sequences.
if (linenoiseProbe() != 0)
linenoiseSetDumbMode(1);
// Tell linenoise where to get command completions.
linenoiseSetCompletionCallback(&esp_console_get_completion);
// Hints trigger additional redraw traffic on every key press.
// Disabling them makes repeated backspace handling much more stable.
// linenoiseSetHintsCallback((linenoiseHintsCallback*)&esp_console_get_hint);
// Set command history size
linenoiseHistorySetMaxLen(100);
// Set command maximum length
linenoiseSetMaxLineLen(console_config.max_cmdline_length);
// Don't return empty lines
linenoiseAllowEmpty(false);
console_update_prompt();
// Register commands
console_register_commands();
log_sram_used(__FILE_NAME__ ": initialize_console end");
}
void console_task(void *arg)
{
_delay_ms(30);
printf("\n"
"Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n");
while (true)
{
char* line = linenoise(prompt);
if (line == NULL)
continue;
if (strlen(line) > 0)
linenoiseHistoryAdd(line);
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND)
printf("Unrecognized command\n");
else if (err == ESP_ERR_INVALID_ARG)
{
}
else if (err == ESP_OK && ret != ESP_OK)
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
else if (err != ESP_OK)
printf("Internal error: %s\n", esp_err_to_name(err));
linenoiseFree(line);
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
void initialize_console();
void console_task(void *arg);
void esp_console_register_system_commands();
+359
View File
@@ -0,0 +1,359 @@
#include "main.h"
//------------------- DeHrust --------------------------
/* BBStream class */
class BBStream
{
private:
u8* base;
u8* p;
int idx;
int len;
bool eof;
u16 bits;
public:
BBStream(u8* from, int blockSize)
{
base = p = from;
len = blockSize;
idx = 0;
eof = false;
bits = getByte();
bits += 256 * getByte();
}
u8 getByte(void)
{
if (p - base == len)
{
eof = true; return 0;
}
return *p++;
}
u8 getBit()
{
u16 mask[] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
u8 bit = (bits & mask[idx]) ? 1 : 0;
if (idx == 15)
{
bits = getByte();
bits += 256 * getByte();
}
idx = (idx + 1) % 16;
return bit;
}
u8 getBits(int n)
{
u8 r = 0;
do
{
r = 2 * r + getBit();
} while(--n);
return r;
}
bool error(void)
{
return eof;
}
};
/* depacker */
u16 dehrust(u8 *dst, u8 *src, int size)
{
BBStream s(src, size);
if (!dst || !src)
return 0;
u8 *to = dst;
*to++ = s.getByte();
u8 noBits = 2;
u8 mask[] = { 0, 0, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0 };
while (!s.error())
{
while (s.getBit())
*to++ = s.getByte();
u16 len = 0;
u8 bb /* = 0*/;
do
{
bb = s.getBits(2);
len += bb;
} while(bb == 0x03 && len != 0x0f);
short offset = 0;
if (len == 0)
{
offset = 0xfff8 + s.getBits(3);
{ u8 t = to[offset]; *to++ = t; }
continue;
}
if (len == 1)
{
u8 code = s.getBits(2);
if (code == 2)
{
u8 b = s.getByte();
if (b >= 0xe0)
{
b <<= 1; ++b; // rlca
b ^= 2; // xor c
if (b == 0xff)
{
++noBits; continue;
}
offset = 0xff00 + b - 0x0f;
{ u8 t = to[offset]; *to++ = t; }
*to++ = s.getByte();
{ u8 t = to[offset]; *to++ = t; }
continue;
}
offset = 0xff00 + b;
}
if (code == 0 || code == 1)
{
offset = s.getByte();
offset += 256 * (code ? 0xfe : 0xfd);
}
if (code == 3)
offset = 0xffe0 + s.getBits(5);
for (u8 i = 0; i < 2; ++i)
{ u8 t = to[offset]; *to++ = t; }
continue;
}
if (len == 3)
{
if (s.getBit())
{
offset = 0xfff0 + s.getBits(4);
{ u8 t = to[offset]; *to++ = t; }
*to++ = s.getByte();
{ u8 t = to[offset]; *to++ = t; }
continue;
}
if (s.getBit())
{
u8 noBytes = 6 + s.getBits(4);
for (u8 i = 0; i < 2 * noBytes; ++i)
*to++ = s.getByte();
continue;
}
len = s.getBits(7);
if (len == 0x0f)
break; // EOF
if (len < 0x0f)
len = 256 * len + s.getByte();
}
if (len == 2)
++len;
u8 code = s.getBits(2);
if (code == 1)
{
u8 b = s.getByte();
if (b >= 0xe0)
{
if (len > 3)
return false;
b <<= 1; ++b; // rlca
b ^= 3; // xor c
offset = 0xff00 + b - 0x0f;
{ u8 t = to[offset]; *to++ = t; }
*to++ = s.getByte();
{ u8 t = to[offset]; *to++ = t; }
continue;
}
offset = 0xff00 + b;
}
if (code == 0)
offset = 0xfe00 + s.getByte();
if (code == 2)
offset = 0xffe0 + s.getBits(5);
if (code == 3)
{
offset = 256 * (mask[noBits] + s.getBits(noBits));
offset += s.getByte();
}
for (u16 i = 0; i < len; ++i)
{ u8 t = to[offset]; *to++ = t; }
}
return to - dst;
}
//------------------- DeMegaLZ --------------------------
class deMLZ
{
private:
u8 *from;
u8 *to;
u8 bitstream;
int bitcount;
public:
deMLZ(u8 *dst, u8 *src)
{
from = src;
to = dst;
}
void init_bitstream(void)
{
bitstream = get_byte();
bitcount = 8;
}
u8 get_byte(void)
{
return *from++;
}
void put_byte(u8 val)
{
*to++ = val;
}
void repeat(u32 disp, int num)
{
for (int i = 0; i < num; i++)
{
u8 val = *(to - disp);
put_byte(val);
}
}
// gets specified number of bits from bitstream
// returns them LSB-aligned
u32 get_bits(int count)
{
u32 bits = 0;
while (count--)
{
if (bitcount--)
{
bits <<= 1;
bits |= 1 & (bitstream >> 7);
bitstream <<= 1;
}
else
{
init_bitstream();
count++; // repeat loop once more
}
}
return bits;
}
int get_bigdisp(void)
{
u32 bits;
// inter displacement
if (get_bits(1))
{
bits = get_bits(4);
return 0x1100 - (bits << 8) - get_byte();
}
// shorter displacement
else
return 256 - get_byte();
}
};
// depacker
void demlz(u8 *dst, u8 *src, int size)
{
deMLZ s(dst, src);
u32 done = 0;
int i;
// get first byte of packed file and write to output
s.put_byte(s.get_byte());
// second byte goes to bitstream
s.init_bitstream();
// actual depacking loop!
do
{
// get 1st bit - either OUTBYTE or beginning of LZ code
// OUTBYTE
if (s.get_bits(1))
s.put_byte(s.get_byte());
// LZ code
else
{
switch (s.get_bits(2))
{
case 0: // 000
s.repeat(8 - s.get_bits(3), 1);
break;
case 1: // 001
s.repeat(256 - s.get_byte(), 2);
break;
case 2: // 010
s.repeat(s.get_bigdisp(), 3);
break;
case 3: // 011
// extract num of length bits
for (i = 1; !s.get_bits(1); i++)
;
// check for exit code
if (i == 9)
done = 1;
else if (i <= 7)
{
// get length bits itself
int bits = s.get_bits(i);
s.repeat(s.get_bigdisp(), 2 + (1 << i) + bits);
}
break;
}
}
} while (!done);
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "main.h"
u16 dehrust(u8 *dst, u8 *src, int size);
void demlz(u8 *dst, u8 *src, int size);
+244
View File
@@ -0,0 +1,244 @@
#include <stdint.h>
#include <string.h>
#include <elf.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "mem_obj.h"
#include "elf.cpp.h"
int load_elf(u8 *elf, MEM_OBJ *obj, int opt)
{
enum
{
TYPE_NONE,
TYPE_TEXT,
TYPE_RODATA,
TYPE_DATA,
TYPE_BSS,
};
Elf32_Ehdr* elf_header = (Elf32_Ehdr*)elf;
Elf32_Shdr* section_headers = (Elf32_Shdr*)(elf + elf_header->e_shoff);
#ifdef VERBOSE
Elf32_Shdr* shstrtab_header = &section_headers[elf_header->e_shstrndx];
const char* shstrtab = (const char*)(elf + shstrtab_header->sh_offset);
#endif
u32 entry = 0;
int size = 0;
int num_sec = elf_header->e_shnum;
Elf32_Rela* rld = NULL;
int rld_num = 0;
// Parse sections to determine type and find dynamic relocation section
for (int i = 0; i < num_sec; i++)
{
Elf32_Shdr* section = &section_headers[i];
if (!section->sh_addr) continue;
section->sh_info = TYPE_NONE;
section->sh_link = 0;
switch (section->sh_type)
{
case SHT_RELA:
section->sh_type = TYPE_NONE;
rld = (Elf32_Rela*)&elf[section->sh_offset];
rld_num = section->sh_size / sizeof(Elf32_Rela);
break;
case SHT_NOBITS:
section->sh_type = TYPE_BSS;
break;
case SHT_PROGBITS:
if (section->sh_flags & SHF_EXECINSTR)
{
section->sh_type = TYPE_TEXT;
section->sh_link = 1; // .text is always used if no symbol refere it
}
else if (section->sh_flags & SHF_WRITE)
section->sh_type = TYPE_DATA;
else
section->sh_type = TYPE_RODATA;
break;
default:
section->sh_type = TYPE_NONE;
}
}
// Parse relocation symbols to find used sections
for (int i = 0; i < rld_num; i++)
{
u32 rld_offs = rld[i].r_offset;
u32 *sym_offs = (u32*)&elf[rld_offs];
u32 sym_val = *sym_offs;
for (int j = 0; j < num_sec; j++)
{
Elf32_Shdr* section = &section_headers[j];
if (section->sh_addr)
{
if ((sym_val >= section->sh_addr) && (sym_val <= (section->sh_addr + section->sh_size - 1)))
{
section->sh_link++;
break;
}
}
}
}
// Allocate memory for sections
#ifdef VERBOSE
printf("\nSections used:\n");
#endif
for (int i = 0; i < num_sec; i++)
{
Elf32_Shdr* section = &section_headers[i];
if (!section->sh_addr) continue;
if (!section->sh_link) continue;
section->sh_info = 0;
switch (section->sh_type)
{
case TYPE_TEXT:
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
if (!section->sh_info) goto cleanup;
entry = elf_header->e_entry - section->sh_offset + section->sh_info + 0x6F0000;
obj->text = (void*)section->sh_info;
obj->sz_text = section->sh_size;
obj->entry = (void*)entry;
size += section->sh_size;
break;
case TYPE_BSS:
#ifdef CONFIG_SPIRAM
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_BSS_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
#else
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
#endif
if (!section->sh_info) goto cleanup;
obj->bss = (void*)section->sh_info;
obj->sz_bss = section->sh_size;
size += section->sh_size;
break;
case TYPE_DATA:
#ifdef CONFIG_SPIRAM
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_DATA_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
#else
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
#endif
if (!section->sh_info) goto cleanup;
obj->data = (void*)section->sh_info;
obj->sz_data = section->sh_size;
size += section->sh_size;
break;
case TYPE_RODATA:
#ifdef CONFIG_SPIRAM
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_RODATA_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
#else
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
#endif
if (!section->sh_info) goto cleanup;
obj->rodata = (void*)section->sh_info;
obj->sz_rodata = section->sh_size;
size += section->sh_size;
break;
}
#ifdef VERBOSE
const char* section_name = shstrtab + section->sh_name;
printf("%-10s Addr: %06lX Size: %-5ld Symbols: %-3ld Alloc: %lX Align: %ld\n", section_name, section->sh_addr, section->sh_size, section->sh_link, section->sh_info, section->sh_addralign);
#endif
}
// Relocate dynamic symbols
#ifdef VERBOSE
printf("\nRelocated symbols:\n");
#endif
for (int i = 0; i < rld_num; i++)
{
u32 rld_offs = rld[i].r_offset;
u32 *sym_offs = (u32*)&elf[rld_offs];
u32 sym_val = *sym_offs;
u32 sym_addr = 0;
#ifdef VERBOSE
const char* name = "";
#endif
for (int j = 0; j < num_sec; j++)
{
Elf32_Shdr* section = &section_headers[j];
if (section->sh_addr)
{
if ((sym_val >= section->sh_addr) && (sym_val <= (section->sh_addr + section->sh_size - 1)))
{
sym_addr = sym_val - section->sh_offset + section->sh_info + ((section->sh_type == TYPE_TEXT) ? 0x6F0000 : 0);
*sym_offs = sym_addr;
#ifdef VERBOSE
name = shstrtab + section->sh_name;
#endif
break;
}
}
}
#ifdef VERBOSE
printf("%-4d Offs: %-5lX Value: %-6lX Addr: %lX Section: %s\n", i, rld_offs, sym_val, sym_addr, name);
#endif
}
// Initialize memory for sections
for (int i = 0; i < num_sec; i++)
{
Elf32_Shdr* section = &section_headers[i];
if (!section->sh_addr) continue;
if (!section->sh_link) continue;
switch (section->sh_type)
{
case TYPE_TEXT:
case TYPE_DATA:
case TYPE_RODATA:
if (section->sh_info) memcpy((void*)section->sh_info, &elf[section->sh_offset], section->sh_size);
break;
case TYPE_BSS:
if (section->sh_info) memset((void*)section->sh_info, 0, section->sh_size);
break;
}
}
#ifdef VERBOSE
printf("\nEntry: %lX\n", entry);
#endif
return size;
cleanup:
if (obj->text) free(obj->text);
if (obj->rodata) free(obj->rodata);
if (obj->data) free(obj->data);
if (obj->bss) free(obj->bss);
return -1;
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
int load_elf(u8 *elf, MEM_OBJ *obj, int opt);
+375
View File
@@ -0,0 +1,375 @@
#pragma once
#define ZIFI_STR_HELPER(x) #x
#define ZIFI_STR(x) ZIFI_STR_HELPER(x)
#define PROD_VER0 0
#define PROD_VER1 6
#define PROD_VER2 4
#define PROD_VER_STRING ZIFI_STR(PROD_VER0) "." ZIFI_STR(PROD_VER1) "." ZIFI_STR(PROD_VER2)
#define CP_STRING "ESP32 SPI WiFi Module, ver." PROD_VER_STRING ", (c) TS-Labs"
#define API_VER 1
#define FEAT_VER 10
#define API_VER_STRING ZIFI_STR(API_VER)
#define FEAT_VER_STRING ZIFI_STR(FEAT_VER)
#define API_STRING "API version: " API_VER_STRING ", feature version: " FEAT_VER_STRING
// Registers
enum
{
ESP_REG_COMMAND = 0x00, // 1 byte
ESP_REG_STATUS = 0x01, // 1 byte
// String commands
ESP_REG_STRING_TYPE = 0x02, // 1 byte
ESP_REG_STRING_SIZE = 0x02, // 1 byte
ESP_REG_STRING_DATA = 0x03, // byte array
// Version commands
ESP_REG_API = 0x02, // 1 byte
ESP_REG_FEAT = 0x03, // 2 bytes
ESP_REG_VER0 = 0x05, // 1 byte
ESP_REG_VER1 = 0x06, // 1 byte
ESP_REG_VER2 = 0x07, // 1 byte
// Object commands and data transmission commands
ESP_REG_OBJ_TYPE = 0x02, // 1 byte
ESP_REG_DATA_SIZE = 0x03, // 4 bytes
ESP_REG_DATA_OFFSET = 0x07, // 4 bytes
ESP_REG_OBJ_HANDLE = 0x0B, // 1 byte
// Lib commands
ESP_REG_FUNC = 0x02, // 1 byte
ESP_REG_OPT = 0x03, // 1 byte
ESP_REG_ARG = 0x03, // 4 bytes
ESP_REG_RETVAL = 0x07, // 4 bytes
ESP_REG_ARR1_HANDLE = 0x0C, // 1 byte
ESP_REG_ARR2_HANDLE = 0x0D, // 1 byte
ESP_REG_ARR3_HANDLE = 0x0E, // 1 byte
ESP_REG_LIB_HANDLE = 0x0F, // 1 byte
// Network commands
ESP_REG_NETSTATE = 0x02,
ESP_REG_IP = 0x2C, // 4 bytes
ESP_REG_OWN_IP = 0x30, // 4 bytes
ESP_REG_MASK = 0x34, // 4 bytes
ESP_REG_GATE = 0x38, // 4 bytes
// Stats commands
ESP_EXEC_TIME = 0x3C, // 4 bytes
// SFX commands
ESP_REG_SFX_CHANNEL = 0x02, // 1 byte
ESP_REG_SFX_GROUP = 0x03, // 1 byte
ESP_REG_SFX_VOLUME = 0x04, // 1 byte
ESP_REG_SFX_PAN = 0x05, // 1 byte
ESP_REG_SFX_PITCH = 0x06, // 2 bytes, Q4.8 little-endian
ESP_REG_SFX_STATE_ACTIVE = 0x10, // 1 byte
ESP_REG_SFX_STATE_HANDLE = 0x11, // 1 byte
ESP_REG_SFX_STATE_GROUP = 0x12, // 1 byte
ESP_REG_SFX_STATE_ORDER = 0x13, // 1 byte
ESP_REG_SFX_STATE_VOLUME = 0x14, // 1 byte
ESP_REG_SFX_STATE_PAN = 0x15, // 1 byte
ESP_REG_SFX_STATE_PITCH = 0x16, // 2 bytes, Q4.8 little-endian
ESP_REG_SFX_STATE_RATE = 0x18, // 4 bytes
ESP_REG_SFX_STATE_FRAMES = 0x1C, // 4 bytes
ESP_REG_SFX_STATE_POSITION = 0x20, // 4 bytes
ESP_REG_SFX_STATE_BITS = 0x24, // 2 bytes
ESP_REG_SFX_STATE_CHANNELS = 0x26, // 1 byte
ESP_REG_SFX_STATE_SIGNED = 0x27, // 1 byte
// Track (XM/MOD/S3M) get-state/get-info commands
ESP_REG_TRACK_STATUS = 0x28, // 1 byte: TRACK_OBJ_ST_STOPPED / TRACK_OBJ_ST_PLAYING
ESP_REG_TRACK_IS_CURRENT = 0x29, // 1 byte: 1 if this handle is the one currently loaded in the player
ESP_REG_TRACK_PATTERN_INDEX = 0x2A, // 1 byte: position in the order/pattern-table list
ESP_REG_TRACK_PATTERN = 0x2B, // 1 byte: pattern number
ESP_REG_TRACK_ROW = 0x2C, // 1 byte: current row within the pattern
ESP_REG_TRACK_SAMPLES = 0x2D, // 4 bytes: samples elapsed since track start
ESP_REG_TRACK_BPM = 0x31, // 2 bytes
ESP_REG_TRACK_TEMPO = 0x33, // 2 bytes
ESP_REG_TRACK_LOOP_COUNT = 0x35, // 1 byte
ESP_REG_TRACK_TOTAL_SAMPLES = 0x36, // 4 bytes: length of one full pass through the song, computed once at load time
ESP_REG_TRACK_MAX_LOOP_COUNT = 0x3A, // 1 byte: see ESP_CMD_TRACK_SET_PARAMS
// VFS bridge commands (SMB2 network share <-> Wild Commander local storage)
ESP_REG_VFS_PENDING = 0x02, // 1 byte: 1 if a request is waiting, else 0
// NOTE: ESP_CMD_VFS_POLL's output also includes ESP_REG_DATA_SIZE (0x03,
// 4 bytes) - VFS_OP must not overlap that range, unlike most register
// reuse in this header, since both are read back from the SAME command.
ESP_REG_VFS_OP = 0x36, // 1 byte: VFS_OP_* (see below) - meaning of the payload from ESP_CMD_VFS_GET_REQUEST_DATA
ESP_REG_VFS_STATUS = 0x02, // 1 byte: i: 0=success, nonzero=error (ESP_CMD_VFS_SUBMIT_RESULT)
};
// Commands
enum
{
ESP_CMD_NOP = 0x00, // (no parameters)
ESP_CMD_GET_INFO_STR = 0x01, // i: ESP_REG_STRING_TYPE
// o: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
ESP_CMD_GET_VER = 0x02, // o: ESP_REG_API, ESP_REG_FEAT, ESP_REG_VER0..2
ESP_CMD_GET_CHIP_INFO = 0x03, // +++
ESP_CMD_GET_NETSTATE = 0x11, // o: ESP_REG_NETSTATE
ESP_CMD_WSCAN = 0x12, // (no parameters)
ESP_CMD_SET_AP_NAME = 0x13, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
ESP_CMD_SET_AP_PWD = 0x14, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
ESP_CMD_AP_CONNECT = 0x16, // (no parameters)
ESP_CMD_AP_DISCONNECT = 0x17, // (no parameters)
ESP_CMD_SET_URL = 0x18, // (no parameters)
ESP_CMD_GET_IP = 0x19, // o: ESP_REG_IP
ESP_CMD_HTTP_GET = 0x20, // +++
ESP_CMD_HTTPS_GET = 0x21, // (no parameters)
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE
ESP_CMD_GOPHER_GET = 0x22, // (no parameters)
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE
ESP_CMD_HTTP_STREAM_START = 0x23, // (no parameters)
// o: ESP_REG_DATA_SIZE (total size)
ESP_CMD_HTTPS_STREAM_START = 0x24, // (no parameters)
// o: ESP_REG_DATA_SIZE (total size)
ESP_CMD_GOPHER_STREAM_START = 0x25, // (no parameters)
// o: ESP_REG_DATA_SIZE (total size)
ESP_CMD_STREAM_READ = 0x26, // (no parameters)
// o: ESP_REG_DATA_SIZE (chunk size)
// (DMA read like ESP_CMD_READ_OBJECT)
ESP_CMD_STREAM_CLOSE = 0x27, // (no parameters)
// VFS bridge: a small generic pipe, not one opcode per filesystem
// operation - the actual op (VFS_OP_STAT/READDIR/OPEN/READ/WRITE/CLOSE/
// DELETE/MKDIR/RMDIR) travels inside the request payload, mirroring how
// the fake filesystem's own Vfs_Dispatch works. The Z80 side polls
// ESP_CMD_VFS_POLL on every idle tick; when a request is pending it
// fetches the payload, does the real filesystem op locally through WC,
// and posts the result back.
ESP_CMD_VFS_SERVER_START = 0x28, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA (share name)
ESP_CMD_VFS_SERVER_STOP = 0x29, // (no parameters)
ESP_CMD_VFS_POLL = 0x2A, // o: ESP_REG_VFS_PENDING, ESP_REG_VFS_OP, ESP_REG_DATA_SIZE (request payload length, if pending)
ESP_CMD_VFS_GET_REQUEST_DATA = 0x2B, // o: ESP_REG_DATA_SIZE (DMA read like ESP_CMD_READ_OBJECT)
ESP_CMD_VFS_SUBMIT_RESULT = 0x2C, // i: ESP_REG_VFS_STATUS, ESP_REG_DATA_SIZE (DMA write like ESP_CMD_WRITE_OBJECT)
ESP_CMD_WEATHER_GET = 0x2D, // i: OBJ_HANDLE -> country\0zip\0 (DATA/DATAF)
// o: new OBJ_HANDLE, DATA_OFFSET=0, DATA_SIZE=90
ESP_CMD_TRACK_INIT = 0xA0, // i: ESP_REG_OBJ_HANDLE
ESP_CMD_TRACK_PLAY = 0xA1, // i: ESP_REG_OBJ_HANDLE
ESP_CMD_TRACK_STOP = 0xA2, // (no parameters)
ESP_CMD_TRACK_RESET = 0xA3, // i: ESP_REG_OBJ_HANDLE, stop playback and reset XMC context to start
ESP_CMD_TRACK_SET_VOLUME = 0xA4, // +++
ESP_CMD_TRACK_SET_S_RATE = 0xA5, // +++
ESP_CMD_TRACK_SET_PARAMS = 0xA6, // i: ESP_REG_OBJ_HANDLE, ESP_REG_TRACK_MAX_LOOP_COUNT
// (0 = loop forever, the default; N = stop playback after N loops)
ESP_CMD_TRACK_SET_POS = 0xA7, // +++
ESP_CMD_TRACK_GET_INFO = 0xA8, // i: ESP_REG_OBJ_HANDLE
// o: ESP_REG_TRACK_STATUS, ESP_REG_TRACK_IS_CURRENT,
// ESP_REG_TRACK_PATTERN_INDEX, ESP_REG_TRACK_PATTERN, ESP_REG_TRACK_ROW,
// ESP_REG_TRACK_SAMPLES, ESP_REG_TRACK_BPM, ESP_REG_TRACK_TEMPO,
// ESP_REG_TRACK_LOOP_COUNT, ESP_REG_TRACK_TOTAL_SAMPLES
// (position/speed reflect the object's own context - live if
// ESP_REG_TRACK_IS_CURRENT is set, otherwise a frozen snapshot from
// when it last played or was reset)
ESP_CMD_TRACK_GET_STATE = 0xA9, // i: ESP_REG_OBJ_HANDLE
// o: ESP_REG_TRACK_STATUS, ESP_REG_TRACK_IS_CURRENT
ESP_CMD_TRACK_GET_STATS = 0xAA, // +++
ESP_CMD_XM_STREAM_LOAD = 0xAB, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
ESP_CMD_MOD_STREAM_LOAD = 0xAC, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
ESP_CMD_S3M_STREAM_LOAD = 0xAD, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
ESP_CMD_SFX_PLAY = 0xB0, // i: ESP_REG_OBJ_HANDLE, ESP_REG_SFX_GROUP; o: ESP_REG_SFX_CHANNEL
ESP_CMD_SFX_PLAY_EX = 0xB1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_SFX_GROUP, ESP_REG_SFX_VOLUME, ESP_REG_SFX_PAN, ESP_REG_SFX_PITCH; o: ESP_REG_SFX_CHANNEL
ESP_CMD_SFX_STOP = 0xB2, // i: ESP_REG_SFX_CHANNEL
ESP_CMD_SFX_SET_PARAMS = 0xB3, // i: ESP_REG_SFX_CHANNEL, ESP_REG_SFX_VOLUME, ESP_REG_SFX_PAN, ESP_REG_SFX_PITCH
ESP_CMD_SFX_SET_VOLUME = 0xB4, // i: ESP_REG_SFX_VOLUME
ESP_CMD_SFX_GET_STATE = 0xB5, // i: ESP_REG_SFX_CHANNEL; o: ESP_REG_SFX_STATE_*
ESP_CMD_SFX_STOP_GROUP = 0xB6, // i: ESP_REG_SFX_GROUP
ESP_CMD_CONFIG_SPI = 0xC0, // +++
ESP_CMD_LOAD_ELF = 0xD0, // i: ESP_REG_OBJ_HANDLE
// o: ESP_REG_LIB_HANDLE
ESP_CMD_LOAD_ELF_OPT = 0xD1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_OPT
// o: ESP_REG_LIB_HANDLE
ESP_CMD_RUN_FUNC0 = 0xD2, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG,
// o: ESP_REG_RETVAL
ESP_CMD_RUN_FUNC1 = 0xD3, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE
// o: ESP_REG_RETVAL
ESP_CMD_RUN_FUNC2 = 0xD4, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE, ESP_REG_ARR2_HANDLE
// o: ESP_REG_RETVAL
ESP_CMD_RUN_FUNC3 = 0xD5, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE, ESP_REG_ARR2_HANDLE, ESP_REG_ARR3_HANDLE
// o: ESP_REG_RETVAL
ESP_CMD_MAKE_OBJECT = 0xE0, // i: ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
ESP_CMD_WRITE_OBJECT = 0xE1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
ESP_CMD_READ_OBJECT = 0xE2, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
ESP_CMD_DELETE_OBJECT = 0xE3, // i: ESP_REG_OBJ_HANDLE
ESP_CMD_KILL_OBJECTS = 0xE4, // (no parameters)
ESP_CMD_FILL_OBJECT = 0xE5, // +++
ESP_CMD_COPY_OBJECT = 0xE6, // +++
ESP_CMD_GET_OBJECT_INFO = 0xE7, // +++
ESP_CMD_REBOOT = 0xED, // (no parameters)
ESP_CMD_RESET = 0xEE, // (no parameters)
ESP_CMD_BREAK = 0xEF, // abort active host tracker stream state, READY if idle
ESP_CMD_GET_RND = 0xF0, // i: ESP_REG_DATA_SIZE
ESP_CMD_DEHST = 0xF1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
ESP_CMD_UNZIP = 0xF2, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
ESP_CMD_STREAM_UNZIP = 0xF3, // i: ESP_REG_OBJ_TYPE
// o: ESP_REG_OBJ_HANDLE
};
enum
{
ESP_OPT_DATA_SRAM = 0x01,
ESP_OPT_RODATA_SRAM = 0x02,
ESP_OPT_BSS_SRAM = 0x04,
ESP_OPT_SPI_1BIT = 0x00,
ESP_OPT_SPI_2BIT = 0x40,
ESP_OPT_SPI_4BIT = 0x80,
};
enum
{
// Status codes
ESP_ST_IDLE = 0x00, // Idle
ESP_ST_READY = 0x01, // Command completed
ESP_ST_BUSY = 0x02, // Busy executing command or initializing
ESP_ST_DATA_M2S = 0x03, // Ready to receive data from Master to Slave
ESP_ST_DATA_S2M = 0x04, // Ready to send data from Slave to Master
ESP_ST_RESET = 0x7E, // Reset performed, ready
// Error codes
ESP_ERR_INV_COMMAND = 0x80,
ESP_ERR_INV_PARAM ,
ESP_ERR_INV_STATE ,
ESP_ERR_INV_STR_LEN ,
ESP_ERR_INV_SIZE , // 84
ESP_ERR_INV_OBJ_TYPE ,
ESP_ERR_INV_ARR_NUM ,
ESP_ERR_INV_HANDLE ,
ESP_ERR_INV_XM_HANDLE , // 88
ESP_ERR_INV_LIB_HANDLE ,
ESP_ERR_INV_ELF_HANDLE ,
ESP_ERR_INV_HST_HANDLE ,
ESP_ERR_INV_BSS_HANDLE , // 8C
ESP_ERR_INV_ARG_HANDLE ,
ESP_ERR_INV_SRC_HANDLE ,
ESP_ERR_INV_DST_HANDLE ,
ESP_ERR_INV_XM , // 90,
ESP_ERR_INV_LIB ,
ESP_ERR_INV_ELF ,
ESP_ERR_INV_HST ,
ESP_ERR_INV_ZIP , // 94
ESP_ERR_INV_MOD ,
ESP_ERR_INV_MOD_HANDLE ,
ESP_ERR_INV_S3M ,
ESP_ERR_INV_S3M_HANDLE , // 98
ESP_ERR_OUT_OF_MEMORY ,
ESP_ERR_OUT_OF_HANDLES ,
ESP_ERR_OBJ_NOT_DELETED ,
ESP_ERR_AP_NOT_CONNECTED = 0xB0,
ESP_ERR_NET_BUSY ,
ESP_ERR_WEATHER = 0xB5, // weather HTTP/JSON failure; details in log
ESP_ERR_VFS_NO_REQUEST = 0x9C, // ESP_CMD_VFS_GET_REQUEST_DATA/SUBMIT_RESULT with nothing pending
ESP_ERR_VFS_TIMEOUT = 0x9D, // no VFS client waiting for a result (e.g. it gave up)
};
// VFS bridge request opcodes - travel inside the ESP_CMD_VFS_GET_REQUEST_DATA
// payload, not as separate ESP_CMD_* values (see ESP_CMD_VFS_POLL comment).
enum
{
VFS_OP_STAT = 0x40,
VFS_OP_READDIR = 0x41,
VFS_OP_OPEN = 0x42,
VFS_OP_READ = 0x43,
VFS_OP_WRITE = 0x44,
VFS_OP_CLOSE = 0x45,
VFS_OP_DELETE = 0x46,
VFS_OP_MKDIR = 0x47,
VFS_OP_RMDIR = 0x48,
// 2026-09-08: real filesystem-size/free-space query, via Wild Commander
// Improved's FILEX_OP_GET_FS_INFO (needs FILEX.WMF installed) - see
// do_statfs() in smbvfs/src/main.c. No request payload. Result: u32
// total_clusters, u32 free_clusters, u8 sectors_per_cluster, u16
// bytes_per_sector (11 bytes) - status=0 on success. Was previously
// faked as a hardcoded 512MB/32MB in vfs_bridge.cpp, see
// project_smbvfs_plugin.md.
VFS_OP_STATFS = 0x49,
};
// Info type (ESP_CMD_GET_INFO_STR)
enum
{
GET_INFO_COPYRIGHT = 0x00,
GET_INFO_API = 0x01,
GET_INFO_BUILD = 0x02,
GET_INFO_IDF = 0x03,
};
// Memory object types (add new types to mem_obj.cpp)
enum
{
OBJ_TYPE_NONE = 0,
OBJ_TYPE_DATA = 1, // SPIRAM
OBJ_TYPE_DATAF = 2, // SRAM
OBJ_TYPE_ELF = 3, // SPIRAM
OBJ_TYPE_LIB = 4, // Virtual
OBJ_TYPE_XM = 5, // SPIRAM
OBJ_TYPE_WAV = 6, // SPIRAM
OBJ_TYPE_HST = 7, // SPIRAM
OBJ_TYPE_ZIP = 8, // SPIRAM
OBJ_TYPE_XMC = 9, // SPIRAM
OBJ_TYPE_MOD = 10, // SPIRAM
OBJ_TYPE_MDC = 11, // SPIRAM
OBJ_TYPE_S3M = 12, // SPIRAM
OBJ_TYPE_S3C = 13, // SPIRAM
OBJ_TYPE_MBC = 14, // SPIRAM
};
// Network state
enum
{
NETWORK_CLOSED = 0x00,
NETWORK_OPENING = 0x01,
NETWORK_OPEN = 0x02,
NETWORK_CLOSING = 0x03,
NETWORK_UNKNOWN = 0xFF
};
// SPI Slave Commands (ESP32)
enum
{
ESP_SPI_CMD_WR_REGS = 0x01,
ESP_SPI_CMD_RD_REGS = 0x02,
ESP_SPI_CMD_WR_DATA = 0x03,
ESP_SPI_CMD_RD_DATA = 0x04,
ESP_SPI_CMD_W_END = 0x07,
ESP_SPI_CMD_R_END = 0x08,
};
enum
{
OBJ_ST_NONE = 0,
LIB_OBJ_ST_READY = 0x10, // Library loaded
TRACK_OBJ_ST_STOPPED = 0x20, // Tracker object context created, idling
TRACK_OBJ_ST_PLAYING = 0x21, // Tracker object playing
WAV_OBJ_ST_PLAYING = 0x30, // WAV object is used by SFX renderer
OBJ_ST_DELETING = 0xFE, // Object is being deleted, new users must not attach
OBJ_ST_ERROR = 0xFF // Reserved
};
+618
View File
@@ -0,0 +1,618 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
#include "esp_console.h"
#include "esp_partition.h"
#include "esp_vfs_fat.h"
#include "diskio_impl.h"
#include "diskio_wl.h"
#include "ff.h"
#include "wear_levelling.h"
#include "fatfs.h"
bool fs_initialized = false;
bool fs_fs_mounted = false;
char fs_fs_base_path[32] = { 0 };
char fs_part_label[16] = { 0 };
wl_handle_t fs_wl_handle = WL_INVALID_HANDLE;
FATFS *fs_fs_fatfs = NULL;
BYTE fs_fs_pdrv = FF_DRV_NOT_USED;
bool fs_fs_vfs_registered = false;
bool fs_fs_diskio_registered = false;
bool fs_fs_fat_mounted = false;
bool fs_fs_no_filesystem = false;
FRESULT fs_fs_last_mount_result = FR_OK;
#define FS_VFS_MAX_FILES 4
int fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size)
{
int n;
if (!base_path || !base_path[0] || !full || full_size < 2) return 0;
if (!path || !path[0] || strcmp(path, "/") == 0)
{
n = snprintf(full, full_size, "%s", base_path);
return n >= 0 && (size_t)n < full_size;
}
if (path[0] == '/')
{
n = snprintf(full, full_size, "%s%s", base_path, path);
return n >= 0 && (size_t)n < full_size;
}
n = snprintf(full, full_size, "%s/%s", base_path, path);
return n >= 0 && (size_t)n < full_size;
}
const esp_partition_t *fs_find_partition()
{
return esp_partition_find_first(
ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_FAT,
FS_PART_LABEL);
}
void fs_clear_mount_state()
{
fs_initialized = false;
fs_fs_mounted = false;
fs_fs_fatfs = NULL;
fs_fs_pdrv = FF_DRV_NOT_USED;
fs_fs_vfs_registered = false;
fs_fs_diskio_registered = false;
fs_fs_fat_mounted = false;
fs_fs_base_path[0] = 0;
fs_part_label[0] = 0;
fs_wl_handle = WL_INVALID_HANDLE;
}
void fs_unmount_force()
{
char drv[3];
if (fs_fs_pdrv != FF_DRV_NOT_USED)
{
drv[0] = (char)('0' + fs_fs_pdrv);
drv[1] = ':';
drv[2] = 0;
if (fs_fs_fat_mounted)
f_mount(NULL, drv, 0);
}
if (fs_fs_diskio_registered && fs_fs_pdrv != FF_DRV_NOT_USED)
{
ff_diskio_unregister(fs_fs_pdrv);
if (fs_wl_handle != WL_INVALID_HANDLE)
ff_diskio_clear_pdrv_wl(fs_wl_handle);
}
if (fs_fs_vfs_registered)
esp_vfs_fat_unregister_path(fs_fs_base_path[0] ? fs_fs_base_path : FS_BASE_PATH);
if (fs_wl_handle != WL_INVALID_HANDLE)
wl_unmount(fs_wl_handle);
fs_clear_mount_state();
}
esp_err_t fs_register_fat(const esp_partition_t *part, const esp_vfs_fat_mount_config_t *mount_cfg)
{
char drv[3];
esp_err_t err;
if (!part || !mount_cfg) return ESP_ERR_INVALID_ARG;
err = wl_mount(part, &fs_wl_handle);
if (err != ESP_OK) return err;
err = ff_diskio_get_drive(&fs_fs_pdrv);
if (err != ESP_OK || fs_fs_pdrv == FF_DRV_NOT_USED)
return ESP_ERR_NO_MEM;
err = ff_diskio_register_wl_partition(fs_fs_pdrv, fs_wl_handle);
if (err != ESP_OK) return err;
fs_fs_diskio_registered = true;
drv[0] = (char)('0' + fs_fs_pdrv);
drv[1] = ':';
drv[2] = 0;
esp_vfs_fat_conf_t fs_fatfs_conf = {
.base_path = FS_BASE_PATH,
.fat_drive = drv,
.max_files = (size_t)mount_cfg->max_files,
};
err = esp_vfs_fat_register(&fs_fatfs_conf, &fs_fs_fatfs);
if (err != ESP_OK) return err;
fs_fs_vfs_registered = true;
fs_fs_last_mount_result = f_mount(fs_fs_fatfs, drv, 1);
if (fs_fs_last_mount_result != FR_OK)
{
fs_fs_no_filesystem = fs_fs_last_mount_result == FR_NO_FILESYSTEM;
return ESP_FAIL;
}
fs_fs_fat_mounted = true;
return ESP_OK;
}
size_t fs_get_allocation_unit_size(size_t part_size)
{
if (part_size >= (16u * 1024u * 1024u)) return 16u * 1024u;
if (part_size >= (4u * 1024u * 1024u)) return 4u * 1024u;
return 4096;
}
void fs_fill_mount_cfg(esp_vfs_fat_mount_config_t *cfg, bool format_if_mount_failed)
{
const esp_partition_t *part = fs_find_partition();
size_t part_size = part ? part->size : 0;
memset(cfg, 0, sizeof(*cfg));
cfg->format_if_mount_failed = format_if_mount_failed;
cfg->max_files = FS_VFS_MAX_FILES;
cfg->allocation_unit_size = fs_get_allocation_unit_size(part_size);
cfg->disk_status_check_enable = false;
cfg->use_one_fat = true;
}
const char *fs_fat_type_name(BYTE fs_type)
{
switch (fs_type)
{
case FS_FAT12: return "FAT12";
case FS_FAT16: return "FAT16";
case FS_FAT32: return "FAT32";
#if defined(FF_FS_EXFAT) && FF_FS_EXFAT
case FS_EXFAT: return "exFAT";
#endif
default: return "unknown";
}
}
uint32_t fs_fat_sector_size(FATFS *fat)
{
if (!fat) return 0;
#if FF_MAX_SS != FF_MIN_SS
return fat->ssize;
#else
return FF_MIN_SS;
#endif
}
FRESULT fs_get_mounted_fatfs(FATFS **out_fat, DWORD *out_free_clusters)
{
if (!out_fat || !out_free_clusters) return FR_INVALID_PARAMETER;
*out_fat = NULL;
*out_free_clusters = 0;
FRESULT res = f_getfree("", out_free_clusters, out_fat);
if (res == FR_OK) return res;
return f_getfree("0:", out_free_clusters, out_fat);
}
void fs_print_fatfs_details(uint64_t total_bytes, uint64_t free_bytes, esp_err_t usage_err)
{
FATFS *fat = NULL;
DWORD free_clusters_fatfs = 0;
FRESULT res = fs_get_mounted_fatfs(&fat, &free_clusters_fatfs);
if (res != FR_OK || !fat)
{
printf(" fatfs : unavailable (FRESULT=%u)\r\n", (unsigned)res);
return;
}
uint32_t sector_size = fs_fat_sector_size(fat);
uint32_t cluster_size = (uint32_t)fat->csize * sector_size;
uint32_t total_clusters = fat->n_fatent >= 2 ? fat->n_fatent - 2 : 0;
uint32_t free_clusters = (uint32_t)free_clusters_fatfs;
if (free_clusters > total_clusters)
free_clusters = total_clusters;
uint32_t used_clusters = total_clusters >= free_clusters ? total_clusters - free_clusters : 0;
uint64_t data_bytes = (uint64_t)total_clusters * cluster_size;
uint64_t fat_bytes = (uint64_t)fat->fsize * sector_size;
printf(" fat type : %s (%u)\r\n", fs_fat_type_name(fat->fs_type), (unsigned)fat->fs_type);
printf(" fat copies : %u\r\n", (unsigned)fat->n_fats);
printf(" sector : %u bytes\r\n", (unsigned)sector_size);
printf(" cluster : %u sectors, %u bytes\r\n", (unsigned)fat->csize, (unsigned)cluster_size);
printf(" clusters : total=%u used=%u free=%u\r\n", (unsigned)total_clusters, (unsigned)used_clusters, (unsigned)free_clusters);
printf(" data area : %llu bytes\r\n", (unsigned long long)data_bytes);
printf(" fat size : %u sectors, %llu bytes each\r\n", (unsigned)fat->fsize, (unsigned long long)fat_bytes);
printf(" root ents : %u\r\n", (unsigned)fat->n_rootdir);
printf(" vol base : %llu\r\n", (unsigned long long)fat->volbase);
printf(" fat base : %llu\r\n", (unsigned long long)fat->fatbase);
printf(" dir base : %llu\r\n", (unsigned long long)fat->dirbase);
printf(" data base : %llu\r\n", (unsigned long long)fat->database);
if (usage_err == ESP_OK && total_bytes != data_bytes)
printf(" vfs total : %llu bytes\r\n", (unsigned long long)total_bytes);
if (usage_err == ESP_OK && cluster_size > 0 && free_bytes != (uint64_t)free_clusters * cluster_size)
printf(" vfs free : %llu bytes\r\n", (unsigned long long)free_bytes);
}
esp_err_t fs_mount_impl(bool quiet)
{
const esp_partition_t *part;
esp_err_t err;
if (fs_fs_mounted)
{
fs_initialized = true;
return ESP_OK;
}
fs_fs_no_filesystem = false;
fs_fs_last_mount_result = FR_OK;
part = fs_find_partition();
if (!part) return ESP_ERR_NOT_FOUND;
esp_vfs_fat_mount_config_t mount_cfg;
fs_fill_mount_cfg(&mount_cfg, false);
err = fs_register_fat(part, &mount_cfg);
if (err != ESP_OK)
{
fs_unmount_force();
if (fs_fs_no_filesystem)
return ESP_ERR_INVALID_STATE;
if (!quiet)
{
if (fs_fs_last_mount_result != FR_OK)
printf("E: FAT f_mount failed: FRESULT=%u\r\n", (unsigned)fs_fs_last_mount_result);
else
printf("E: FAT mount failed: %s\r\n", esp_err_to_name(err));
}
return err;
}
fs_initialized = true;
fs_fs_mounted = true;
fs_fs_no_filesystem = false;
fs_fs_last_mount_result = FR_OK;
snprintf(fs_fs_base_path, sizeof(fs_fs_base_path), "%s", FS_BASE_PATH);
snprintf(fs_part_label, sizeof(fs_part_label), "%s", FS_PART_LABEL);
return ESP_OK;
}
esp_err_t fs_mount()
{
return fs_mount_impl(false);
}
esp_err_t fs_mount_quiet()
{
return fs_mount_impl(true);
}
esp_err_t fs_format()
{
const esp_partition_t *part = fs_find_partition();
if (!part) return ESP_ERR_NOT_FOUND;
if (fs_fs_mounted)
fs_deinit();
esp_vfs_fat_mount_config_t format_cfg;
fs_fill_mount_cfg(&format_cfg, true);
esp_err_t err = esp_vfs_fat_spiflash_format_cfg_rw_wl(
FS_BASE_PATH,
FS_PART_LABEL,
&format_cfg);
if (err != ESP_OK)
{
printf("E: esp_vfs_fat_spiflash_format_cfg_rw_wl failed: %s\r\n", esp_err_to_name(err));
return err;
}
return fs_mount();
}
esp_err_t fs_delete()
{
const esp_partition_t *part = fs_find_partition();
if (!part) return ESP_ERR_NOT_FOUND;
fs_deinit();
esp_err_t err = esp_partition_erase_range(part, 0, part->size);
if (err == ESP_OK)
{
fs_fs_no_filesystem = true;
fs_fs_last_mount_result = FR_NO_FILESYSTEM;
}
return err;
}
void fs_deinit()
{
fs_unmount_force();
fs_fs_no_filesystem = false;
fs_fs_last_mount_result = FR_OK;
}
esp_err_t fs_ensure_ready()
{
return fs_mount();
}
esp_err_t fs_ensure_ready_quiet()
{
return fs_mount_quiet();
}
esp_err_t fs_list_dir(const char *base_path, const char *path)
{
char full[256];
if (!fs_build_full_path(base_path, path, full, sizeof(full)))
return ESP_ERR_INVALID_ARG;
errno = 0;
DIR *d = opendir(full);
if (!d)
{
int saved_errno = errno;
printf("E: opendir('%s') failed, errno=%d\r\n", full, saved_errno);
return ESP_FAIL;
}
printf("Listing: %s\r\n", full);
for (;;)
{
struct dirent *e = readdir(d);
if (!e) break;
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
continue;
char p[256];
int n = snprintf(p, sizeof(p), "%s/%s", full, e->d_name);
if (n < 0 || (size_t)n >= sizeof(p))
{
printf(" ? ? %s (path too long)\r\n", e->d_name);
continue;
}
struct stat st = {};
if (stat(p, &st) == 0)
{
const char *t = S_ISDIR(st.st_mode) ? "DIR " : "FILE";
printf(" %s %10" PRIu32 " %s\r\n", t, (uint32_t)st.st_size, e->d_name);
}
else
{
printf(" ? ? %s\r\n", e->d_name);
}
}
closedir(d);
return ESP_OK;
}
int fs_info(int argc, char **argv)
{
const esp_partition_t *part;
uint64_t total = 0;
uint64_t free = 0;
uint64_t used = 0;
esp_err_t err;
if (argc < 0 || !argv) return 1;
err = fs_ensure_ready_quiet();
part = fs_find_partition();
printf("FAT info\r\n");
printf(" label : %s\r\n", FS_PART_LABEL);
printf(" base path : %s\r\n", FS_BASE_PATH);
if (err != ESP_OK)
{
if (err == ESP_ERR_NOT_FOUND)
{
printf(" state : partition not found\r\n");
return 0;
}
if (fs_fs_no_filesystem)
{
printf(" state : no FAT filesystem\r\n");
if (part)
{
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
}
return 0;
}
if (fs_fs_last_mount_result != FR_OK)
printf(" state : unavailable (FRESULT=%u)\r\n", (unsigned)fs_fs_last_mount_result);
else
printf(" state : unavailable (%s)\r\n", esp_err_to_name(err));
return 1;
}
err = esp_vfs_fat_info(FS_BASE_PATH, &total, &free);
if (err == ESP_OK)
used = total - free;
printf(" mounted : %s\r\n", fs_fs_mounted ? "yes" : "no");
esp_vfs_fat_mount_config_t cfg;
fs_fill_mount_cfg(&cfg, false);
printf(" cfg alloc : %u bytes\r\n", (unsigned)cfg.allocation_unit_size);
printf(" cfg fats : %u on next format\r\n", cfg.use_one_fat ? 1u : 2u);
printf(" max files : %d\r\n", cfg.max_files);
if (err == ESP_OK)
{
printf(" total : %llu bytes\r\n", (unsigned long long)total);
printf(" used : %llu bytes\r\n", (unsigned long long)used);
printf(" free : %llu bytes\r\n", (unsigned long long)free);
}
else
{
printf(" usage : unavailable (%s)\r\n", esp_err_to_name(err));
}
fs_print_fatfs_details(total, free, err);
if (part)
{
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
}
else
{
printf(" partition : not found\r\n");
}
if (fs_wl_handle != WL_INVALID_HANDLE)
{
size_t wl_bytes = wl_size(fs_wl_handle);
size_t wl_sector = wl_sector_size(fs_wl_handle);
printf(" wl size : %u bytes\r\n", (unsigned)wl_bytes);
printf(" wl sector : %u bytes\r\n", (unsigned)wl_sector);
printf(" wl handle : %d\r\n", (int)fs_wl_handle);
}
return 0;
}
esp_err_t fs_init()
{
return fs_format();
}
int fs_format_cmd(int argc, char **argv)
{
if (argc < 0 || !argv) return 1;
const esp_partition_t *part = fs_find_partition();
if (!part)
{
printf("E: partition '%s' not found\r\n", FS_PART_LABEL);
return 1;
}
esp_vfs_fat_mount_config_t format_cfg;
fs_fill_mount_cfg(&format_cfg, true);
esp_err_t err = fs_format();
if (err != ESP_OK) return 1;
printf("FAT formatted and mounted: %s -> %s\r\n", FS_PART_LABEL, FS_BASE_PATH);
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
printf(" alloc unit : %u bytes\r\n", (unsigned)format_cfg.allocation_unit_size);
printf(" fat copies : %u\r\n", format_cfg.use_one_fat ? 1u : 2u);
return 0;
}
int fs_delete_cmd(int argc, char **argv)
{
const esp_partition_t *part;
esp_err_t err;
if (argc < 0 || !argv) return 1;
part = fs_find_partition();
if (!part)
{
printf("E: partition '%s' not found\r\n", FS_PART_LABEL);
return 1;
}
err = fs_delete();
if (err != ESP_OK)
{
printf("E: FAT erase failed: %s\r\n", esp_err_to_name(err));
return 1;
}
printf("FAT erased: %s\r\n", FS_PART_LABEL);
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
return 0;
}
int fs_ls(int argc, char **argv)
{
const char *path = "/";
if (argc >= 3)
path = argv[2];
esp_err_t err = fs_ensure_ready();
if (err != ESP_OK) return 1;
err = fs_list_dir(FS_BASE_PATH, path);
return (err == ESP_OK) ? 0 : 1;
}
int fs_cmd(int argc, char **argv)
{
if (argc < 2 || !argv[1])
{
printf("Usage:\r\n");
printf(" fat format\r\n");
printf(" fat info\r\n");
printf(" fat del\r\n");
printf(" fat ls [path]\r\n");
return 0;
}
const char *op = argv[1];
if (!strcmp(op, "format"))
return fs_format_cmd(argc, argv);
if (!strcmp(op, "info"))
return fs_info(argc, argv);
if (!strcmp(op, "del"))
return fs_delete_cmd(argc, argv);
if (!strcmp(op, "ls"))
return fs_ls(argc, argv);
printf("Unknown subcommand: %s\r\n", op);
return 1;
}
void fat_console_register_system_commands()
{
const esp_console_cmd_t cmd =
{
.command = "fat",
.help = "FATFS commands: format/info/del/ls",
.hint = NULL,
.func = &fs_cmd,
.argtable = NULL
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "wear_levelling.h"
#define FS_PART_LABEL "fat"
#define FS_BASE_PATH "/fs"
extern bool fs_initialized;
extern bool fs_fs_mounted;
extern char fs_fs_base_path[32];
extern char fs_part_label[16];
extern wl_handle_t fs_wl_handle;
esp_err_t fs_mount();
esp_err_t fs_mount_quiet();
esp_err_t fs_format();
esp_err_t fs_delete();
esp_err_t fs_init();
esp_err_t fs_ensure_ready();
esp_err_t fs_ensure_ready_quiet();
void fs_deinit();
esp_err_t fs_list_dir(const char *base_path, const char *path);
int fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size);
int fs_info(int argc, char **argv);
int fs_format_cmd(int argc, char **argv);
int fs_delete_cmd(int argc, char **argv);
int fs_ls(int argc, char **argv);
int fs_cmd(int argc, char **argv);
void fat_console_register_system_commands();
+174
View File
@@ -0,0 +1,174 @@
const u8 code_866_fnt[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x82, 0xaa, 0x82,
0x82, 0xba, 0x92, 0x7c, 0x7c, 0xfe, 0xd6, 0xfe, 0xfe, 0xc6, 0xee, 0x7c,
0x44, 0xee, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x10, 0x38, 0x7c, 0xfe,
0x7c, 0x38, 0x10, 0x00, 0x38, 0x38, 0xd6, 0xfe, 0xd6, 0x10, 0x38, 0x00,
0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, 0x38, 0x7c,
0x7c, 0x38, 0x00, 0x00, 0xfe, 0xfe, 0xc6, 0x82, 0x82, 0xc6, 0xfe, 0xfe,
0x00, 0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0xfe, 0xfe, 0xc6, 0xba,
0xba, 0xc6, 0xfe, 0xfe, 0x00, 0x0e, 0x06, 0x7a, 0xd8, 0x88, 0xd8, 0x70,
0x38, 0x6c, 0x44, 0x6c, 0x38, 0x10, 0x38, 0x10, 0x1e, 0x12, 0x1e, 0x10,
0x10, 0x70, 0xe0, 0x40, 0x3e, 0x22, 0x3e, 0x22, 0x22, 0x2e, 0xe4, 0x40,
0x92, 0x7c, 0x6c, 0xc6, 0x6c, 0x7c, 0x92, 0x00, 0x80, 0xe0, 0xf8, 0xfe,
0xf8, 0xe0, 0x80, 0x00, 0x02, 0x0e, 0x3e, 0xfe, 0x3e, 0x0e, 0x02, 0x00,
0x10, 0x38, 0x7c, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x6c, 0x6c, 0x6c, 0x6c,
0x6c, 0x00, 0x6c, 0x00, 0x7e, 0xb6, 0xb6, 0x76, 0x36, 0x36, 0x36, 0x00,
0x3c, 0x60, 0x3c, 0x66, 0x3c, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7c, 0x7c, 0x7c, 0x00, 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x7c,
0x10, 0x38, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10,
0x7c, 0x38, 0x10, 0x00, 0x00, 0x08, 0x04, 0x7e, 0x04, 0x08, 0x00, 0x00,
0x00, 0x10, 0x20, 0x7e, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x40, 0x7e, 0x00, 0x00, 0x00, 0x28, 0x6c, 0xfe, 0x6c, 0x28, 0x00, 0x00,
0x10, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0xfe, 0x00, 0xfe, 0x7c, 0x7c, 0x38,
0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, 0x00, 0x6c, 0x24, 0x48,
0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x7e, 0x24, 0x24, 0x7e, 0x24, 0x00,
0x18, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x18, 0x00, 0x63, 0x66, 0x0c, 0x18,
0x30, 0x66, 0xc6, 0x00, 0x00, 0x18, 0x34, 0x18, 0x2c, 0x66, 0x3a, 0x00,
0x00, 0x18, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x18,
0x18, 0x0c, 0x06, 0x00, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00,
0x00, 0x24, 0x18, 0x7e, 0x18, 0x24, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30,
0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00,
0x00, 0x3c, 0x66, 0x6e, 0x76, 0x66, 0x3c, 0x00, 0x00, 0x18, 0x38, 0x18,
0x18, 0x18, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x06, 0x3c, 0x60, 0x7e, 0x00,
0x00, 0x3c, 0x66, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x66, 0x66, 0x66,
0x7e, 0x06, 0x06, 0x00, 0x00, 0x7e, 0x60, 0x7c, 0x06, 0x66, 0x3c, 0x00,
0x00, 0x3c, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x06, 0x06,
0x0c, 0x18, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00,
0x00, 0x3c, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x10, 0x20,
0x06, 0x0c, 0x18, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x3c, 0x00,
0x3c, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00,
0x00, 0x3c, 0x66, 0x06, 0x1c, 0x00, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x5a,
0x5e, 0x60, 0x3c, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
0x00, 0x7c, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x3c, 0x66, 0x60,
0x60, 0x66, 0x3c, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00,
0x00, 0x7e, 0x60, 0x78, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x7e, 0x60, 0x60,
0x7c, 0x60, 0x60, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x6e, 0x66, 0x3e, 0x00,
0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x18, 0x18,
0x18, 0x18, 0x3c, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3c, 0x00,
0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x60, 0x60, 0x60,
0x60, 0x60, 0x7e, 0x00, 0x00, 0x82, 0xc6, 0xee, 0xd6, 0xc6, 0xc6, 0x00,
0x00, 0x46, 0x66, 0x76, 0x6e, 0x66, 0x62, 0x00, 0x00, 0x3c, 0x66, 0x66,
0x66, 0x66, 0x3c, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00,
0x00, 0x3c, 0x66, 0x66, 0x66, 0x6e, 0x3c, 0x06, 0x00, 0x7c, 0x66, 0x66,
0x7c, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x60, 0x3c, 0x06, 0x66, 0x3c, 0x00,
0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66,
0x66, 0x66, 0x3c, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x24, 0x18, 0x00,
0x00, 0xc6, 0xc6, 0xc6, 0xd6, 0xee, 0x44, 0x00, 0x00, 0x66, 0x66, 0x3c,
0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x00,
0x00, 0x7e, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x1e, 0x18, 0x18, 0x18,
0x18, 0x18, 0x1e, 0x00, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x00,
0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, 0x00, 0x18, 0x3c, 0x66,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x00, 0x18, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06,
0x3e, 0x66, 0x3e, 0x00, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00,
0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x06, 0x06, 0x3e,
0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00,
0x00, 0x1c, 0x36, 0x30, 0x78, 0x30, 0x30, 0x00, 0x00, 0x00, 0x3e, 0x66,
0x66, 0x3e, 0x06, 0x3c, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00,
0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x0c, 0x00, 0x0c,
0x0c, 0x0c, 0x0c, 0x38, 0x00, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00,
0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, 0xfc, 0xd6,
0xd6, 0xd6, 0xd6, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00,
0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7c, 0x66,
0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06,
0x00, 0x00, 0x3c, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3c, 0x60,
0x3c, 0x06, 0x7c, 0x00, 0x00, 0x30, 0x78, 0x30, 0x30, 0x30, 0x1c, 0x00,
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x66, 0x66,
0x66, 0x24, 0x18, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xee, 0x44, 0x00,
0x00, 0x00, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66,
0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00,
0x1c, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x1c, 0x00, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x00, 0x70, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x70, 0x00,
0x00, 0x34, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x99, 0xa1,
0xa1, 0x99, 0x42, 0x3c, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
0x00, 0x7c, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x7c, 0x66, 0x7c,
0x66, 0x66, 0x7c, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00,
0x00, 0x1e, 0x36, 0x66, 0x66, 0x66, 0xff, 0xc3, 0x00, 0x7e, 0x60, 0x78,
0x60, 0x60, 0x7e, 0x00, 0x00, 0xd6, 0xd6, 0x7c, 0x7c, 0xd6, 0xd6, 0x00,
0x00, 0x3c, 0x66, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x62, 0x66, 0x6e,
0x76, 0x66, 0x46, 0x00, 0x04, 0x6a, 0x66, 0x6e, 0x76, 0x66, 0x46, 0x00,
0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x1e, 0x36, 0x66,
0x66, 0x66, 0x66, 0x00, 0x00, 0x82, 0xc6, 0xee, 0xd6, 0xc6, 0xc6, 0x00,
0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66,
0x66, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00,
0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3c, 0x66, 0x60,
0x60, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x7c, 0xd6, 0xd6,
0xd6, 0x7c, 0x10, 0x00, 0x00, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x66, 0x00,
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7f, 0x01, 0x00, 0x66, 0x66, 0x66,
0x3e, 0x06, 0x06, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x00,
0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xff, 0x01, 0x00, 0xe0, 0x60, 0x7c,
0x66, 0x66, 0x7c, 0x00, 0x00, 0xc6, 0xc6, 0xf6, 0xde, 0xde, 0xf6, 0x00,
0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x3c, 0x66, 0x0e,
0x06, 0x66, 0x3c, 0x00, 0x00, 0xce, 0xdb, 0xfb, 0xdb, 0xdb, 0xce, 0x00,
0x00, 0x3e, 0x66, 0x66, 0x3e, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x06,
0x3e, 0x66, 0x3e, 0x00, 0x00, 0x3c, 0x60, 0x3c, 0x66, 0x66, 0x3c, 0x00,
0x00, 0x00, 0x7c, 0x66, 0x7c, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x06,
0x3c, 0x60, 0x3e, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x3c, 0x00,
0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x00, 0x00, 0xd6, 0x7c,
0x7c, 0xd6, 0xd6, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x0c, 0x66, 0x3c, 0x00,
0x00, 0x00, 0x62, 0x66, 0x6e, 0x76, 0x66, 0x00, 0x00, 0x04, 0x6a, 0x66,
0x6e, 0x76, 0x66, 0x00, 0x00, 0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00,
0x00, 0x00, 0x1e, 0x36, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x82, 0xc6,
0xee, 0xd6, 0xc6, 0x00, 0x00, 0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x66,
0x66, 0x66, 0x66, 0x00, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88,
0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x77, 0xdd, 0x77, 0xdd,
0x77, 0xdd, 0x77, 0xdd, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18,
0xf8, 0x18, 0x18, 0x18, 0x66, 0x66, 0x66, 0xe6, 0x66, 0x66, 0x66, 0x66,
0x00, 0x00, 0x00, 0xfe, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0xf8, 0x18,
0xf8, 0x18, 0x18, 0x18, 0x66, 0x66, 0xe6, 0x06, 0xe6, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0xfe, 0x06,
0xe6, 0x66, 0x66, 0x66, 0x66, 0x66, 0xe6, 0x06, 0xfe, 0x00, 0x00, 0x00,
0x66, 0x66, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0xf8, 0x18,
0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x66, 0x66, 0x66, 0x67,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x60, 0x7f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7f, 0x60, 0x67, 0x66, 0x66, 0x66, 0x66, 0x66, 0xe7, 0x00,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xe7, 0x66, 0x66, 0x66,
0x66, 0x66, 0x67, 0x60, 0x67, 0x66, 0x66, 0x66, 0x00, 0x00, 0xff, 0x00,
0xff, 0x00, 0x00, 0x00, 0x66, 0x66, 0xe7, 0x00, 0xe7, 0x66, 0x66, 0x66,
0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18,
0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7f,
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x7f,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xff, 0x66, 0x66, 0x66, 0x66,
0x18, 0x18, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x18, 0x18, 0x18,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60,
0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x18,
0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c,
0x00, 0x00, 0x7c, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x66, 0x66,
0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7f, 0x01,
0x00, 0x00, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x00, 0x00, 0x00, 0xd6, 0xd6,
0xd6, 0xd6, 0xfe, 0x00, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xff, 0x01,
0x00, 0x00, 0xe0, 0x60, 0x7c, 0x66, 0x7c, 0x00, 0x00, 0x00, 0xc6, 0xc6,
0xf6, 0xde, 0xf6, 0x00, 0x00, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x7c, 0x00,
0x00, 0x00, 0x3c, 0x66, 0x0e, 0x66, 0x3c, 0x00, 0x00, 0x00, 0xce, 0xdb,
0xfb, 0xdb, 0xce, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x3e, 0x66, 0x66, 0x00,
0x24, 0x7e, 0x60, 0x78, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x24, 0x3c, 0x66,
0x7e, 0x60, 0x3c, 0x00, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0x00,
0x18, 0x30, 0x60, 0x30, 0x18, 0x00, 0x7c, 0x00, 0x00, 0x0e, 0x1b, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00,
0x00, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00,
0x76, 0xdc, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x18, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x0c, 0x0c, 0x6c, 0x3c, 0x1c, 0x00,
0x8f, 0xcd, 0xef, 0xfc, 0xdc, 0xcc, 0xcc, 0x00, 0x38, 0x6c, 0x18, 0x30,
0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x00,
0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x00
};
+8408
View File
File diff suppressed because it is too large Load Diff
+584
View File
@@ -0,0 +1,584 @@
#pragma once
#include <stdint.h>
#include "main.h"
// Types
typedef struct
{
u8 f_mul; // PLL multiplier
u8 f_div; // Pixel Clock divisor
u16 h_fporch; // Horizontal front porch size
u16 h_sync; // Horizontal sync size
u16 h_bporch; // Horizontal back porch size
u16 h_visible; // Horizontal visible area size
u16 v_fporch; // Vertical front porch size
u16 v_sync; // Vertical sync size
u16 v_bporch; // Vertical back porch size
u16 v_visible; // Vertical visible area size
} FT_MODE;
/*
| # | visible | Fpll MHz | Fpix MHz | clocks/line | lines/frame | line kHz | frame Hz |
| -- | -------- | -------- | -------- | ----------- | ----------- | -------- | -------- |
| 0 | 640x480 | 48 | 24 | 800 | 524 | 30.000 | 57.252 |
| 1 | 640x480 | 64 | 32 | 832 | 520 | 38.462 | 73.964 |
| 2 | 640x480 | 64 | 32 | 800 | 524 | 40.000 | 76.336 |
| 3 | 800x600 | 40 | 40 | 1056 | 628 | 37.879 | 60.317 |
| 4 | 800x600 | 80 | 40 | 1056 | 628 | 37.879 | 60.317 |
| 5 | 800x600 | 48 | 48 | 1040 | 666 | 46.154 | 69.300 |
| 6 | 800x600 | 56 | 56 | 1048 | 631 | 53.435 | 84.683 |
| 7 | 1024x768 | 64 | 64 | 1344 | 806 | 47.619 | 59.081 |
| 8 | 1024x768 | 72 | 72 | 1328 | 806 | 54.217 | 67.267 |
| 9 | 1024x768 | 80 | 80 | 1312 | 800 | 60.976 | 76.220 |
| 10 | 640x1024 | 56 | 56 | 844 | 1066 | 66.351 | 62.243 |
| 11 | 1280x720 | 72 | 72 | 1650 | 750 | 43.636 | 58.182 |
| 12 | 1280x720 | 72 | 72 | 1600 | 750 | 45.000 | 60.000 |
*/
enum // const FT_MODE ft_modes[] in ft8xx.c
{
FT_MODE_640_480_57 = 0, // 0: 640x480@57Hz (48MHz)
FT_MODE_640_480_74 = 1, // 1: 640x480@74Hz (64MHz)
FT_MODE_640_480_76 = 2, // 2: 640x480@76Hz (64MHz)
FT_MODE_800_600_60 = 3, // 3: 800x600@60Hz (40MHz)
FT_MODE_800_600_60_80MHZ = 4, // 4: 800x600@60Hz (80MHz)
FT_MODE_800_600_69 = 5, // 5: 800x600@69Hz (48MHz)
FT_MODE_800_600_85 = 6, // 6: 800x600@85Hz (56MHz)
FT_MODE_1024_768_59 = 7, // 7: 1024x768@59Hz (64MHz)
FT_MODE_1024_768_67 = 8, // 8: 1024x768@67Hz (72MHz)
FT_MODE_1024_768_76 = 9, // 9: 1024x768@76Hz (80MHz)
FT_MODE_1280_1024_60_HALF = 10, // 10: 1280/2x1024@60Hz (56MHz)
FT_MODE_1280_720_58 = 11, // 11: 1280x720@58Hz (72MHz)
FT_MODE_1280_720_60 = 12, // 12: 1280x720@60Hz (72MHz)
FT_MODE_MAX
};
// Memory addresses
#define FT_RAM_G 0x000000 // Main graphics RAM
#define FT_ROM_CHIPID 0x0C0000 // Chip ID and revision
#define FT_ROM_FONT 0x1E0000 // Fonts
#define FT_ROM_FONT_ADDR 0x2FFFFC // Font table pointer address
#define FT_RAM_DL 0x300000 // Display list RAM
#define FT_RAM_REG 0x302000 // Registers
#define FT_RAM_CMD 0x308000 // Coprocessor command buffer
#define FT_RAM_ERR_REPORT 0x309800 // Error message
// Commands
#define FT_CMD_ACTIVE 0x00 // cc 00 00
#define FT_CMD_STANDBY 0x41 // cc 00 00
#define FT_CMD_SLEEP 0x42 // cc 00 00
#define FT_CMD_PWRDOWN 0x43 // cc 00 00
#define FT_CMD_CLKEXT 0x44 // cc 00 00
#define FT_CMD_CLKINT 0x48 // cc 00 00
#define FT_CMD_PDROMS 0x49 // cc xx 00
#define FT_CMD_CLKSEL 0x61 // cc xx 00 -> [5:0] - mul, [7:6] - PLL range (0 for mul=0..3, 1 for mul=4..5)
#define FT_CMD_RST_PULSE 0x68 // cc 00 00
// ID
#define FT_ID 0x7C
// Registers
#define FT_REG_ID 0x302000
#define FT_REG_FRAMES 0x302004
#define FT_REG_CLOCK 0x302008
#define FT_REG_FREQUENCY 0x30200C
#define FT_REG_RENDERMODE 0x302010
#define FT_REG_SNAPY 0x302014
#define FT_REG_SNAPSHOT 0x302018
#define FT_REG_SNAPFORMAT 0x30201C
#define FT_REG_CPURESET 0x302020
#define FT_REG_TAP_CRC 0x302024
#define FT_REG_TAP_MASK 0x302028
#define FT_REG_HCYCLE 0x30202C
#define FT_REG_HOFFSET 0x302030
#define FT_REG_HSIZE 0x302034
#define FT_REG_HSYNC0 0x302038
#define FT_REG_HSYNC1 0x30203C
#define FT_REG_VCYCLE 0x302040
#define FT_REG_VOFFSET 0x302044
#define FT_REG_VSIZE 0x302048
#define FT_REG_VSYNC0 0x30204C
#define FT_REG_VSYNC1 0x302050
#define FT_REG_DLSWAP 0x302054
#define FT_REG_ROTATE 0x302058
#define FT_REG_OUTBITS 0x30205C
#define FT_REG_DITHER 0x302060
#define FT_REG_SWIZZLE 0x302064
#define FT_REG_CSPREAD 0x302068
#define FT_REG_PCLK_POL 0x30206C
#define FT_REG_PCLK 0x302070
#define FT_REG_TAG_X 0x302074
#define FT_REG_TAG_Y 0x302078
#define FT_REG_TAG 0x30207C
#define FT_REG_VOL_PB 0x302080
#define FT_REG_VOL_SOUND 0x302084
#define FT_REG_SOUND 0x302088
#define FT_REG_PLAY 0x30208C
#define FT_REG_GPIO_DIR 0x302090
#define FT_REG_GPIO 0x302094
#define FT_REG_GPIOX_DIR 0x302098
#define FT_REG_GPIOX 0x30209C
#define FT_REG_INT_FLAGS 0x3020A8
#define FT_REG_INT_EN 0x3020AC
#define FT_REG_INT_MASK 0x3020B0
#define FT_REG_PLAYBACK_START 0x3020B4
#define FT_REG_PLAYBACK_LENGTH 0x3020B8
#define FT_REG_PLAYBACK_READPTR 0x3020BC
#define FT_REG_PLAYBACK_FREQ 0x3020C0
#define FT_REG_PLAYBACK_FORMAT 0x3020C4
#define FT_REG_PLAYBACK_LOOP 0x3020C8
#define FT_REG_PLAYBACK_PLAY 0x3020CC
#define FT_REG_PWM_HZ 0x3020D0
#define FT_REG_PWM_DUTY 0x3020D4
#define FT_REG_MACRO_0 0x3020D8
#define FT_REG_MACRO_1 0x3020DC
#define FT_REG_CMD_READ 0x3020F8
#define FT_REG_CMD_WRITE 0x3020FC
#define FT_REG_CMD_DL 0x302100
#define FT_REG_TOUCH_MODE 0x302104
#define FT_REG_TOUCH_ADC_MODE 0x302108
#define FT_REG_TOUCH_CHARGE 0x30210C
#define FT_REG_TOUCH_SETTLE 0x302110
#define FT_REG_TOUCH_OVERSAMPLE 0x302114
#define FT_REG_TOUCH_RZTHRESH 0x302118
#define FT_REG_TOUCH_RAW_XY 0x30211C
#define FT_REG_TOUCH_RZ 0x302120
#define FT_REG_TOUCH_SCREEN_XY 0x302124
#define FT_REG_TOUCH_TAG_XY 0x302128
#define FT_REG_TOUCH_TAG 0x30212C
#define FT_REG_TOUCH_TAG1_XY 0x302130
#define FT_REG_TOUCH_TAG1 0x302134
#define FT_REG_TOUCH_TAG2_XY 0x302138
#define FT_REG_TOUCH_TAG2 0x30213C
#define FT_REG_TOUCH_TAG3_XY 0x302140
#define FT_REG_TOUCH_TAG3 0x302144
#define FT_REG_TOUCH_TAG4_XY 0x302148
#define FT_REG_TOUCH_TAG4 0x30214C
#define FT_REG_TOUCH_TRANSFORM_A 0x302150
#define FT_REG_TOUCH_TRANSFORM_B 0x302154
#define FT_REG_TOUCH_TRANSFORM_C 0x302158
#define FT_REG_TOUCH_TRANSFORM_D 0x30215C
#define FT_REG_TOUCH_TRANSFORM_E 0x302160
#define FT_REG_TOUCH_TRANSFORM_F 0x302164
#define FT_REG_TOUCH_CONFIG 0x302168
#define FT_REG_CTOUCH_TOUCH4_X 0x30216C
#define FT_REG_BIST_EN 0x302174
#define FT_REG_TRIM 0x302180
#define FT_REG_ANA_COMP 0x302184
#define FT_REG_SPI_WIDTH 0x302188
#define FT_SPI_WIDTH_SINGLE 0
#define FT_SPI_WIDTH_DUAL 1
#define FT_SPI_WIDTH_QUAD 2
#define FT_REG_TOUCH_DIRECT_XY 0x30218C
#define FT_REG_TOUCH_DIRECT_Z1Z2 0x302190
#define FT_REG_DATESTAMP 0x302564
#define FT_REG_CMDB_SPACE 0x302574
#define FT_REG_CMDB_WRITE 0x302578
#define FT_REG_ADAPTIVE_FRAMERATE 0x30257C
#define FT_REG_PLAYBACK_PAUSE 0x3025EC
#define FT_REG_FLASH_STATUS 0x3025F0
#define REG_MEDIAFIFO_READ 0x309014
#define REG_MEDIAFIFO_WRITE 0x309018
#define FT_REG_FLASH_SIZE 0x309024
#define FT_REG_TRACKER 0x309000
#define FT_REG_TRACKER_1 0x309004
#define FT_REG_TRACKER_2 0x309008
#define FT_REG_TRACKER_3 0x30900C
#define FT_REG_TRACKER_4 0x309010
#define FT_REG_MEDIAFIFO_READ 0x309014
#define FT_REG_MEDIAFIFO_WRITE 0x309018
// Co-processor commands
#define FT_CCMD_APPEND 0xFFFFFF1E
#define FT_CCMD_BGCOLOR 0xFFFFFF09
#define FT_CCMD_BITMAP_TRANSFORM 0xFFFFFF21
#define FT_CCMD_BUTTON 0xFFFFFF0D
#define FT_CCMD_CALIBRATE 0xFFFFFF15
#define FT_CCMD_CLOCK 0xFFFFFF14
#define FT_CCMD_COLDSTART 0xFFFFFF32
#define FT_CCMD_CRC 0xFFFFFF03
#define FT_CCMD_CSKETCH 0xFFFFFF35
#define FT_CCMD_DIAL 0xFFFFFF2D
#define FT_CCMD_DLSTART 0xFFFFFF00
#define FT_CCMD_EXECUTE 0xFFFFFF07
#define FT_CCMD_FGCOLOR 0xFFFFFF0A
#define FT_CCMD_FLASHATTACH 0xFFFFFF49
#define FT_CCMD_FLASHDETACH 0xFFFFFF48
#define FT_CCMD_FLASHERASE 0xFFFFFF44
#define FT_CCMD_FLASHFAST 0xFFFFFF4A
#define FT_CCMD_FLASHRX 0xFFFFFF4D
#define FT_CCMD_FLASHSOURCE 0xFFFFFF4E
#define FT_CCMD_FLASHSPIDESEL 0xFFFFFF4B
#define FT_CCMD_FLASHTX 0xFFFFFF4C
#define FT_CCMD_FLASHUPDATE 0xFFFFFF47
#define FT_CCMD_GAUGE 0xFFFFFF13
#define FT_CCMD_GETMATRIX 0xFFFFFF33
#define FT_CCMD_GETPOINT 0xFFFFFF08
#define FT_CCMD_GETPROPS 0xFFFFFF25
#define FT_CCMD_GETPTR 0xFFFFFF23
#define FT_CCMD_GRADCOLOR 0xFFFFFF34
#define FT_CCMD_GRADIENT 0xFFFFFF0B
#define FT_CCMD_HAMMERAUX 0xFFFFFF04
#define FT_CCMD_IDCT_DELETED 0xFFFFFF06
#define FT_CCMD_INFLATE 0xFFFFFF22
#define FT_CCMD_INTERRUPT 0xFFFFFF02
#define FT_CCMD_INT_RAMSHARED 0xFFFFFF3D
#define FT_CCMD_INT_SWLOADIMAGE 0xFFFFFF3E
#define FT_CCMD_KEYS 0xFFFFFF0E
#define FT_CCMD_LOADIDENTITY 0xFFFFFF26
#define FT_CCMD_LOADIMAGE 0xFFFFFF24
#define FT_CCMD_LOGO 0xFFFFFF31
#define FT_CCMD_MARCH 0xFFFFFF05
#define FT_CCMD_MEDIAFIFO 0xFFFFFF39
#define FT_CCMD_MEMCPY 0xFFFFFF1D
#define FT_CCMD_MEMCRC 0xFFFFFF18
#define FT_CCMD_MEMSET 0xFFFFFF1B
#define FT_CCMD_MEMWRITE 0xFFFFFF1A
#define FT_CCMD_MEMZERO 0xFFFFFF1C
#define FT_CCMD_NUMBER 0xFFFFFF2E
#define FT_CCMD_PLAYVIDEO 0xFFFFFF3A
#define FT_CCMD_PROGRESS 0xFFFFFF0F
#define FT_CCMD_REGREAD 0xFFFFFF19
#define FT_CCMD_ROMFONT 0xFFFFFF3F
#define FT_CCMD_ROTATE 0xFFFFFF29
#define FT_CCMD_SCALE 0xFFFFFF28
#define FT_CCMD_SCREENSAVER 0xFFFFFF2F
#define FT_CCMD_SCROLLBAR 0xFFFFFF11
#define FT_CCMD_SETBASE 0xFFFFFF38
#define FT_CCMD_SETBITMAP 0xFFFFFF43
#define FT_CCMD_SETFONT 0xFFFFFF2B
#define FT_CCMD_SETFONT2 0xFFFFFF3B
#define FT_CCMD_SETMATRIX 0xFFFFFF2A
#define FT_CCMD_SETROTATE 0xFFFFFF36
#define FT_CCMD_SETSCRATCH 0xFFFFFF3C
#define FT_CCMD_SKETCH 0xFFFFFF30
#define FT_CCMD_SLIDER 0xFFFFFF10
#define FT_CCMD_SNAPSHOT 0xFFFFFF1F
#define FT_CCMD_SNAPSHOT2 0xFFFFFF37
#define FT_CCMD_SPINNER 0xFFFFFF16
#define FT_CCMD_STOP 0xFFFFFF17
#define FT_CCMD_SWAP 0xFFFFFF01
#define FT_CCMD_SYNC 0xFFFFFF42
#define FT_CCMD_TEXT 0xFFFFFF0C
#define FT_CCMD_TOGGLE 0xFFFFFF12
#define FT_CCMD_TOUCH_TRANSFORM 0xFFFFFF20
#define FT_CCMD_TRACK 0xFFFFFF2C
#define FT_CCMD_TRANSLATE 0xFFFFFF27
#define FT_CCMD_VIDEOFRAME 0xFFFFFF41
#define FT_CCMD_VIDEOSTART 0xFFFFFF40
#define FT_OPT_CENTER 1536UL
#define FT_OPT_CENTERX 512UL
#define FT_OPT_CENTERY 1024UL
#define FT_OPT_FLAT 256UL
#define FT_OPT_MONO 1UL
#define FT_OPT_NOBACK 4096UL
#define FT_OPT_NODL 2UL
#define FT_OPT_NOHANDS 49152UL
#define FT_OPT_NOHM 16384UL
#define FT_OPT_NOPOINTER 16384UL
#define FT_OPT_NOSECS 32768UL
#define FT_OPT_NOTICKS 8192UL
#define FT_OPT_RIGHTX 2048UL
#define FT_OPT_SIGNED 256UL
#define OPT_NOTEAR 4UL
#define OPT_FULLSCREEN 8UL
#define OPT_MEDIAFIFO 16UL
#define OPT_SOUND 32UL
// Primitives
#define FT_BITMAPS 1
#define FT_POINTS 2
#define FT_LINES 3
#define FT_LINE_STRIP 4
#define FT_EDGE_STRIP_R 5
#define FT_EDGE_STRIP_L 6
#define FT_EDGE_STRIP_A 7
#define FT_EDGE_STRIP_B 8
#define FT_RECTS 9
// Formats
#define FT_ARGB1555 0
#define FT_L1 1
#define FT_L4 2
#define FT_L8 3
#define FT_RGB332 4
#define FT_ARGB2 5
#define FT_ARGB4 6
#define FT_RGB565 7
#define FT_TEXT8X8 9
#define FT_TEXTVGA 10
#define FT_BARGRAPH 11
#define FT_PALETTED565 14
#define FT_PALETTED4444 15
#define FT_PALETTED8 16
#define FT_L2 17
// Blend functions
#define FT_ZERO 0
#define FT_ONE 1
#define FT_SRC_ALPHA 2
#define FT_DST_ALPHA 3
#define FT_ONE_MINUS_SRC_ALPHA 4
#define FT_ONE_MINUS_DST_ALPHA 5
// Stencil test functions
#define FT_NEVER 0
#define FT_LESS 1
#define FT_LEQUAL 2
#define FT_GREATER 3
#define FT_GEQUAL 4
#define FT_EQUAL 5
#define FT_NOTEQUAL 6
#define FT_ALWAYS 7
// Stencil operations
#define FT_KEEP 1
#define FT_REPLACE 2
#define FT_INCR 3
#define FT_DECR 4
#define FT_INVERT 5
#define FT_INCR_WRAP 6 // undocumented???
#define FT_DECR_WRAP 7 // undocumented???
// Bitmap wrap mode
#define FT_REPEAT 1
#define FT_BORDER 0
// Bitmap filtering mode
#define FT_NEAREST 0
#define FT_BILINEAR 1
// DL_SWAP modes
#define FT_DLSWAP_DONE 0
#define FT_DLSWAP_LINE 1
#define FT_DLSWAP_FRAME 2
// INT sources
#define FT_INT_SWAP 1
#define FT_INT_TOUCH 2
#define FT_INT_TAG 4
#define FT_INT_SOUND 8
#define FT_INT_PLAYBACK 16
#define FT_INT_CMDEMPTY 32
#define FT_INT_CMDFLAG 64
#define FT_INT_CONVCOMPLETE 128
// Playback formats
#define FT_LINEAR_SAMPLES 0
#define FT_ULAW_SAMPLES 1
#define FT_ADPCM_SAMPLES 2
// Touch ADC modes
#define FT_ADC_SINGLE_ENDED 0
#define FT_ADC_DIFFERENTIAL 1
// Touch modes
#define FT_TOUCHMODE_OFF 0
#define FT_TOUCHMODE_ONESHOT 1
#define FT_TOUCHMODE_FRAME 2
#define FT_TOUCHMODE_CONTINUOUS 3
// Constants
#define FT_DL_SIZE 8192 // 8KB Display List buffer size
#define FT_CMD_FIFO_SIZE 4096 // 4KB coprocessor Fifo size
#define FT_CMD_SIZE 4 // 4 byte per coprocessor command of EVE
#define FT_GPU_NUMCHAR_PERFONT 128
#define FT_GPU_FONT_TABLE_SIZE 148
// Co-processor
void ft_Append(u32 ptr, u32 num);
void ft_BgColor(u32 c);
void ft_BitmapTransform(i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, i32 tx0, i32 ty0, i32 tx1, i32 ty1, i32 tx2, i32 ty2, u16 result);
void ft_Button(i16 x, i16 y, i16 w, i16 h, i16 font, u16 options, const char *s);
void ft_Calibrate(u32 result);
void ft_Clock(i16 x, i16 y, i16 r, u16 options, u16 h, u16 m, u16 s, u16 ms);
void ft_ColdStart();
void ft_Dial(i16 x, i16 y, i16 r, u16 options, u16 val);
void ft_Dlstart();
void ft_FgColor(u32 c);
void ft_FlashAttach();
void ft_FlashDetach();
void ft_FlashErase();
void ft_FlashFast(u32);
void ft_FlashRx(u32, u32);
void ft_FlashSpiDesel();
void ft_FlashTx(u32);
void ft_FlashUpdate(u32, u32, u32);
void ft_Gauge(i16 x, i16 y, i16 r, u16 options, u16 major, u16 minor, u16 val, u16 range);
void ft_GetMatrix(i32 a, i32 b, i32 c, i32 d, i32 e, i32 f);
void ft_GetProps(u32 ptr, u32 w, u32 h);
void ft_GetPtr(u32 result);
void ft_GradColor(u32 c);
void ft_Gradient(i16 x0, i16 y0, u32 rgb0, i16 x1, i16 y1, u32 rgb1);
void ft_Inflate(u32 ptr);
void ft_Int_RAMShared(u32 ptr);
void ft_Int_SWLoadImage(u32 ptr, u32 options);
void ft_Interrupt(u32 ms);
void ft_Keys(i16 x, i16 y, i16 w, i16 h, i16 font, u16 options, const char *s);
void ft_LoadIdentity();
void ft_LoadImage(u32 ptr, u32 options);
void ft_Logo();
void ft_MediaFifo(u32 ptr, u32 size);
void ft_MemCrc(u32 ptr, u32 num, u32 result);
void ft_MemSet(u32 ptr, u32 value, u32 num);
void ft_MemWrite(u32 ptr, u32 num);
void ft_MemZero(u32 ptr, u32 num);
void ft_Memcpy(u32 dest, u32 src, u32 num);
void ft_Number(i16 x, i16 y, i16 font, u16 options, i32 n);
void ft_PlayVideo(u32 options);
void ft_Progress(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 range);
void ft_RegRead(u32 ptr, u32 result);
void ft_RomFont(u32 font, u32 romslot);
void ft_Rotate(i32 a);
void ft_Scale(i32 sx, i32 sy);
void ft_ScreenSaver();
void ft_Scrollbar(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 size, u16 range);
void ft_SetBase(u32 base);
void ft_SetBitmap(u32 source, u16 fmt, u16 w, u16 h);
void ft_SetFont(u32 font, u32 ptr);
void ft_SetFont2(u32 font, u32 ptr, u32 firstchar);
void ft_SetMatrix();
void ft_SetRotate(u32 r);
void ft_SetScratch(u32 handle);
void ft_Sketch(i16 x, i16 y, u16 w, u16 h, u32 ptr, u16 format);
void ft_Slider(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 range);
void ft_Snapshot(u32 ptr);
void ft_Snapshot2(u32 fmt, u32 ptr, i16 x, i16 y, i16 w, i16 h);
void ft_Spinner(i16 x, i16 y, u16 style, u16 scale);
void ft_Stop();
void ft_Swap();
void ft_Sync();
void ft_Text(i16 x, i16 y, i16 font, u16 options, const char *s);
void ft_Toggle(i16 x, i16 y, i16 w, i16 font, u16 options, u16 state, const char *s);
void ft_TouchTransform(i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, i32 tx0, i32 ty0, i32 tx1, i32 ty1, i32 tx2, i32 ty2, u16 result);
void ft_Track(i16 x, i16 y, i16 w, i16 h, i16 tag);
void ft_Translate(i32 tx, i32 ty);
void ft_VideoFrame(u32 dst, u32 ptr);
void ft_VideoStart();
// Display list
void ft_AlphaFunc(u8 func, u8 ref);
void ft_Begin(u8 prim);
void ft_BitmapHandle(u8 handle);
void ft_BitmapLayout(u8 format, u16 linestride, u16 height);
void ft_BitmapSize(u8 filter, u8 wrapx, u8 wrapy, u16 width, u16 height);
void ft_BitmapSource(u32 addr);
void ft_BitmapTransformA(i32 a);
void ft_BitmapTransformB(i32 b);
void ft_BitmapTransformC(i32 c);
void ft_BitmapTransformD(i32 d);
void ft_BitmapTransformE(i32 e);
void ft_BitmapTransformF(i32 f);
void ft_BlendFunc(u8 src, u8 dst);
void ft_Call(u16 dest);
void ft_Cell(u8 cell);
void ft_Clear(u8 c, u8 s, u8 t);
void ft_ClearAll();
void ft_ClearColorA(u8 alpha);
void ft_ClearColorRGB(u8 red, u8 green, u8 blue);
void ft_ClearColorRGB32(u32 rgb);
void ft_ClearStencil(u8 s);
void ft_ClearTag(u8 s);
void ft_ColorA(u8 alpha);
void ft_ColorMask(u8 r, u8 g, u8 b, u8 a);
void ft_ColorRGB(u8 red, u8 green, u8 blue);
void ft_ColorRGB32(u32 rgb);
void ft_Display();
void ft_End();
void ft_Jump(u16 dest);
void ft_LineWidth(u16 width);
void ft_Macro(u8 m);
void ft_PaletteSource(u32 addr);
void ft_PointSize(u16 size);
void ft_RestoreContext();
void ft_Return();
void ft_SaveContext();
void ft_ScissorSize(u16 width, u16 height);
void ft_ScissorXY(u16 x, u16 y);
void ft_StencilFunc(u8 func, u8 ref, u8 mask);
void ft_StencilMask(u8 mask);
void ft_StencilOp(u8 sfail, u8 spass);
void ft_Tag(u8 s);
void ft_TagMask(u8 mask);
void ft_Vertex2f(i16 x, i16 y);
void ft_Vertex2ii(u16 x, u16 y, u8 handle, u8 cell);
void ft_VertexFormat(u8 f);
void ft_VertexTranslateX(i32 v);
void ft_VertexTranslateY(i32 v);
// Common
void init_ft8xx();
// Transport / mode switch
esp_err_t ft_host_begin();
esp_err_t ft_host_end();
// Command buffer helpers
void ft_ccmd_start(void *addr);
void ft_ccmd(u32 a);
void ft_cstr(const char *s);
esp_err_t ft_ccmd_write();
// RAM / register access
u8 ft_rreg8(u32 a);
u16 ft_rreg16(u32 a);
u32 ft_rreg32(u32 a);
void ft_wreg8(u32 a, u8 v);
void ft_wreg16(u32 a, u16 v);
void ft_wreg32(u32 a, u32 v);
esp_err_t ft_open_session();
esp_err_t ft_close_session();
esp_err_t ft_read(void *addr, u32 ft_addr, u32 size);
esp_err_t ft_write(const void *addr, u32 ft_addr, u32 size);
esp_err_t ft_write_dl(const void *addr, u32 size_dwords);
// Host commands
esp_err_t ft_cmd(u8 a);
esp_err_t ft_cmdp(u8 a, u8 v);
// Coprocessor helpers
esp_err_t ft_cp_wait(uint32_t timeout_ms);
esp_err_t ft_cp_reset();
// Display / init
esp_err_t ft_set_mode(u8 mode);
void ft_swap();
esp_err_t ft_wait_swap(uint32_t timeout_ms);
extern const FT_MODE ft_modes[];
// Math
i16 rcos(i16 r, u16 th);
i16 rsin(i16 r, u16 th);
extern const u16 sintab[];
extern u32 *ft_ccmdb;
extern u16 ft_ccmdp;
// Text attribute display backend
void ft_text_attr_mode_get_size(uint16_t *cols, uint16_t *rows);
esp_err_t ft_text_attr_mode_begin();
esp_err_t ft_text_attr_mode_show();
esp_err_t ft_text_attr_mode_clear(u8 ch, u8 attr);
esp_err_t ft_text_attr_mode_present(const u8 *chars, const u8 *attrs);
void ft_text_attr_mode_end();
// JPEG / RGB888 and DXP viewer helpers
int ft_jpg_ext_match(const char *name);
int ft_dxp_ext_match(const char *name);
esp_err_t ft_jpg_show_file_mode(const char *path, bool stretch, bool quiet = false);
esp_err_t ft_jpg_show_file(const char *path);
esp_err_t ft_dxp_show_file_mode(const char *path, bool quiet = false);
esp_err_t ft_dxp_show_file(const char *path);
esp_err_t ft_rgb888_640x480_show_current(bool stretch);
// Console
void ft_console_register_system_commands();
+252
View File
@@ -0,0 +1,252 @@
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "mem_obj.h"
#include "helper.h"
#include "gopher_client.h"
static const char *TAG = "gopher";
#define GOPHER_BUF_SIZE (2 * 1024 * 1024)
static uint8_t *gopher_buf = NULL;
static size_t gopher_buf_size = 0;
static int gopher_sock = -1;
void gopher_buf_free()
{
if (!gopher_buf) return;
heap_caps_free(gopher_buf);
gopher_buf = NULL;
gopher_buf_size = 0;
ESP_LOGI(TAG, "buffer freed");
}
bool gopher_buf_alloc()
{
if (gopher_buf) return true;
gopher_buf = (uint8_t*)heap_caps_malloc(GOPHER_BUF_SIZE, MALLOC_CAP_SPIRAM);
if (!gopher_buf)
{
ESP_LOGW(TAG, "SPIRAM buffer alloc failed, size=%u", (unsigned)GOPHER_BUF_SIZE);
return false;
}
gopher_buf_size = GOPHER_BUF_SIZE;
ESP_LOGI(TAG, "buffer allocated in SPIRAM: %p, size=%u", gopher_buf, (unsigned)gopher_buf_size);
return true;
}
void gopher_close_socket()
{
if (gopher_sock < 0) return;
close(gopher_sock);
gopher_sock = -1;
}
void gopher_cleanup()
{
gopher_close_socket();
gopher_buf_free();
}
void gopher_init()
{
gopher_buf = NULL;
gopher_buf_size = 0;
gopher_sock = -1;
ESP_LOGI(TAG, "init done");
}
void gopher_do_get()
{
ESP_LOGI(TAG, "GOPHER_GET");
if (!gopher_buf_alloc())
{
wr_reg8(ESP_REG_STATUS, 0xA0);
return;
}
if (!net.url[0])
{
ESP_LOGW(TAG, "URL not set");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
char * url = (char*)net.url;
if (strncmp(url, "gopher://", 9) != 0)
{
ESP_LOGW(TAG, "invalid URL format");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
ESP_LOGI(TAG, "GET %s", url);
char host[256];
int port = 70;
char selector[256];
memset(host, 0, sizeof(host));
memset(selector, 0, sizeof(selector));
selector[0] = '\0';
const char * p = url + 9;
const char * slash = strchr(p, '/');
const char * colon = strchr(p, ':');
if (colon && (!slash || colon < slash))
{
int host_len = colon - p;
strncpy(host, p, host_len);
host[host_len] = 0;
port = atoi(colon + 1);
if (slash)
{
const char * sel_start = slash + 1;
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
sel_start += 2;
strncpy(selector, sel_start, sizeof(selector) - 1);
selector[sizeof(selector) - 1] = 0;
}
}
else if (slash)
{
int host_len = slash - p;
strncpy(host, p, host_len);
host[host_len] = 0;
const char * sel_start = slash + 1;
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
sel_start += 2;
strncpy(selector, sel_start, sizeof(selector) - 1);
selector[sizeof(selector) - 1] = 0;
}
else
{
strncpy(host, p, sizeof(host) - 1);
host[sizeof(host) - 1] = 0;
}
ESP_LOGI(TAG, "host=%s port=%d selector=%s", host, port, selector);
struct hostent * server = gethostbyname(host);
if (!server)
{
ESP_LOGW(TAG, "DNS lookup failed");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0xB1);
return;
}
gopher_sock = socket(AF_INET, SOCK_STREAM, 0);
if (gopher_sock < 0)
{
ESP_LOGW(TAG, "socket creation failed");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
if (connect(gopher_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
{
ESP_LOGW(TAG, "connection failed");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
ESP_LOGI(TAG, "connected");
char request[512];
memset(request, 0, sizeof(request));
snprintf(request, sizeof(request), "/%s\r\n", selector);
int sent = write(gopher_sock, request, strlen(request));
if (sent < 0)
{
ESP_LOGW(TAG, "send failed");
gopher_cleanup();
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
ESP_LOGI(TAG, "sent %d bytes", sent);
int total = 0;
int len;
int last_progress = 0;
while ((len = read(gopher_sock, (char*)gopher_buf + total, gopher_buf_size - total)) > 0)
{
total += len;
if ((size_t)total >= gopher_buf_size) break;
if (total - last_progress >= 65536)
{
wr_reg32(ESP_REG_DATA_SIZE, total);
last_progress = total;
}
vTaskDelay(1);
}
gopher_close_socket();
ESP_LOGI(TAG, "recv %d bytes total", total);
if (total == 0)
{
ESP_LOGW(TAG, "empty response");
gopher_buf_free();
wr_reg8(ESP_REG_STATUS, 0xB4);
return;
}
int resp = make_obj(total, OBJ_TYPE_DATA);
if (resp < 0)
{
ESP_LOGW(TAG, "make_obj failed");
gopher_buf_free();
wr_reg8(ESP_REG_STATUS, 0xA0);
return;
}
memcpy(mem_obj[resp].addr, gopher_buf, total);
gopher_buf_free();
wr_reg8(ESP_REG_OBJ_HANDLE, resp);
wr_reg32(ESP_REG_DATA_SIZE, total);
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
ESP_LOGI(TAG, "done: handle=%d size=%d", resp, total);
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
void gopher_init(void);
void gopher_do_get(void);
+806
View File
@@ -0,0 +1,806 @@
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "miniz.h"
#include "spi_slave.h"
#include "mem_obj.h"
#include "elf.cpp.h"
#include "depack.h"
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
#include "wifi.h"
#include "http_client.h"
#include "weather.h"
#include "gopher_client.h"
#include "stream_client.h"
#endif
#include "helper.h"
#include "tracker.h"
#include "sfx.h"
#include "vfs_bridge.h"
const char TAG[] = "helper";
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
EXT_RAM_BSS_ATTR u8 url_buf[1024];
#endif
QueueHandle_t helper_queue;
NET net;
#include <stdio.h>
#include <string.h>
#include "miniz.h"
#include "esp_log.h"
void IRAM_ATTR put_helper_isr(int task)
{
xQueueSendFromISR(helper_queue, &task, 0);
portYIELD_FROM_ISR();
}
void *f_malloc(size_t x)
{
#ifdef VERBOSE
printf("f_malloc %u\r\n", x);
#endif
return malloc_spiram(x);
}
void f_free(void *p)
{
#ifdef VERBOSE
printf("f_free\r\n");
#endif
free(p);
}
void *f_realloc(void *p, size_t x)
{
#ifdef VERBOSE
printf("f_realloc %u\r\n", x);
#endif
auto m = malloc_spiram(x);
memcpy(m, p, x);
free(p);
return m;
}
u8 sfx_status_from_err(esp_err_t err)
{
switch (err)
{
case ESP_OK:
return ESP_ST_READY;
case ESP_ERR_INVALID_ARG:
return ESP_ERR_INV_PARAM;
case ESP_ERR_INVALID_SIZE:
return ESP_ERR_INV_SIZE;
case ESP_ERR_NOT_FOUND:
return ESP_ERR_INV_STATE;
case ESP_ERR_NOT_SUPPORTED:
case ESP_ERR_INVALID_RESPONSE:
return ESP_ERR_INV_PARAM;
case ESP_ERR_NO_MEM:
return ESP_ERR_OUT_OF_HANDLES;
default:
return ESP_ERR_INV_STATE;
}
}
void helper_task(void *arg)
{
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
net.url = url_buf;
memset(net.url, 0, 1024);
#endif
while (1)
{
int task;
if (xQueueReceive(helper_queue, &task, portMAX_DELAY))
{
switch (task)
{
case TASK_RUN_FUNC0:
case TASK_RUN_FUNC1:
case TASK_RUN_FUNC2:
case TASK_RUN_FUNC3:
{
int handle = rd_reg8(ESP_REG_LIB_HANDLE);
int func = rd_reg8(ESP_REG_FUNC);
int arg = rd_reg32(ESP_REG_ARG);
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_LIB))
{
set_status(ESP_ERR_INV_LIB_HANDLE);
break;
}
int arr1h = 0;
int arr2h = 0;
int arr3h = 0;
if (task >= TASK_RUN_FUNC1)
{
arr1h = rd_reg8(ESP_REG_ARR1_HANDLE);
if (!check_handle(arr1h) || ((mem_obj[arr1h].type != OBJ_TYPE_DATA) && (mem_obj[arr1h].type != OBJ_TYPE_DATAF)))
{
set_status(ESP_ERR_INV_ARG_HANDLE);
#ifdef VERBOSE
printf("Handle error, arr1h %d, type %d\r\n", arr1h, mem_obj[arr1h].type);
#endif
break;
}
}
if (task >= TASK_RUN_FUNC2)
{
arr2h = rd_reg8(ESP_REG_ARR2_HANDLE);
if (!check_handle(arr2h) || ((mem_obj[arr2h].type != OBJ_TYPE_DATA) && (mem_obj[arr2h].type != OBJ_TYPE_DATAF)))
{
set_status(ESP_ERR_INV_ARG_HANDLE);
break;
}
}
if (task == TASK_RUN_FUNC3)
{
arr3h = rd_reg8(ESP_REG_ARR3_HANDLE);
if (!check_handle(arr3h) || ((mem_obj[arr3h].type != OBJ_TYPE_DATA) && (mem_obj[arr3h].type != OBJ_TYPE_DATAF)))
{
set_status(ESP_ERR_INV_ARG_HANDLE);
break;
}
}
void *entry = mem_obj[handle].entry;
void *arr1 = mem_obj[arr1h].addr;
void *arr2 = mem_obj[arr2h].addr;
void *arr3 = mem_obj[arr3h].addr;
int rc = 0;
switch (task)
{
case TASK_RUN_FUNC0:
{
typedef int (*F0)(int func, int arg);
F0 f = (F0)entry;
rc = f(func, arg);
}
break;
case TASK_RUN_FUNC1:
{
typedef int (*F1)(int func, int arg, void *arr1);
F1 f = (F1)entry;
rc = f(func, arg, arr1);
}
break;
case TASK_RUN_FUNC2:
{
typedef int (*F2)(int func, int arg, void *arr1, void *arr2);
F2 f = (F2)entry;
rc = f(func, arg, arr1, arr2);
}
break;
case TASK_RUN_FUNC3:
{
typedef int (*F3)(int func, int arg, void *arr1, void *arr2, void *arr3);
F3 f = (F3)entry;
rc = f(func, arg, arr1, arr2, arr3);
}
break;
}
#ifdef VERBOSE
printf("RC: 0x%08lX\r\n", (u32)rc);
#endif
wr_reg32(ESP_REG_RETVAL, rc);
set_status(ESP_ST_READY);
}
break;
case TASK_LOAD_ELF:
case TASK_LOAD_ELF_OPT:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
int opt = (task == TASK_LOAD_ELF_OPT) ? rd_reg8(ESP_REG_OPT) : 0;
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_ELF))
{
set_status(ESP_ERR_INV_ELF_HANDLE);
break;
}
int new_size = load_elf((u8*)mem_obj[handle].addr, &mem_obj[handle], opt);
if (new_size == -1)
set_status(ESP_ERR_OUT_OF_MEMORY);
else
{
// Delete ELF from memory and change object type to library
if (mem_obj[handle].addr) free(mem_obj[handle].addr);
mem_obj[handle].addr = (void*)-1;
mem_obj[handle].size = new_size;
mem_obj[handle].type = OBJ_TYPE_LIB;
mem_obj[handle].state = LIB_OBJ_ST_READY;
wr_reg8(ESP_REG_LIB_HANDLE, handle);
set_status(ESP_ST_READY);
}
}
break;
case TASK_MAKE_OBJ:
{
size_t obj_size = rd_reg32(ESP_REG_DATA_SIZE);
int obj_type = rd_reg8(ESP_REG_OBJ_TYPE);
int obj_hdl = make_obj(obj_size, obj_type); // set_status is done there
if (obj_hdl >= 0)
{
wr_reg8(ESP_REG_OBJ_HANDLE, obj_hdl);
wr_reg32(ESP_REG_DATA_OFFSET, 0);
wr_reg32(ESP_REG_DATA_SIZE, min(obj_size, DMA_BUF_SIZE));
set_status(ESP_ST_READY);
}
}
break;
case TASK_DEL_OBJ:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
if (!check_handle(handle))
{
set_status(ESP_ERR_INV_HANDLE);
ESP_LOGE(TAG, "Wrong handle! (%02X)", handle);
break;
}
auto rc = delete_obj(handle);
if (!rc)
{
set_status(ESP_ERR_OBJ_NOT_DELETED);
ESP_LOGE(TAG, "Object cannot be deleted! (%02X)", handle);
}
else
{
set_status(ESP_ST_READY);
#ifdef VERBOSE
printf("Object deleted: handle %02X\r\n", handle);
#endif
}
}
break;
case TASK_KILL_OBJ:
{
while (1)
{
int playing = -1;
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
{
if (mem_obj[i].addr && (mem_obj[i].type == OBJ_TYPE_XMC || mem_obj[i].type == OBJ_TYPE_MDC || mem_obj[i].type == OBJ_TYPE_S3C) && mem_obj[i].state == TRACK_OBJ_ST_PLAYING)
{
playing = i;
break;
}
}
if (playing < 0) break;
TRACK_TASK t = {};
t.task = TRACK_TASK_STOP;
t.handle = playing;
xQueueSend(xm_queue, &t, portMAX_DELAY);
while (mem_obj[playing].addr && mem_obj[playing].state == TRACK_OBJ_ST_PLAYING)
vTaskDelay(1);
}
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
{
if (check_handle(i))
{
auto rc = delete_obj(i);
if (!rc)
ESP_LOGE(TAG, "Object cannot be deleted! (%02X)", i);
#ifdef VERBOSE
else
printf("Object deleted: handle %02X\r\n", i);
#endif
}
}
set_status(ESP_ST_READY);
}
break;
case TASK_DEHST:
{
int hsrc = rd_reg8(ESP_REG_OBJ_HANDLE);
u32 ssize = rd_reg32(ESP_REG_DATA_SIZE);
int dtype = rd_reg8(ESP_REG_OBJ_TYPE);
if (!check_handle(hsrc))
{
set_status(ESP_ERR_INV_HST_HANDLE);
break;
}
if (mem_obj[hsrc].type != OBJ_TYPE_HST)
{
set_status(ESP_ERR_INV_OBJ_TYPE);
break;
}
// Check source size
if (!ssize || (ssize > 65536))
{
set_status(ESP_ERR_INV_SIZE);
break;
}
// Unpack the HST
u8 *tmp = (u8*)malloc_spiram(65536);
if (!tmp)
{
set_status(ESP_ERR_OUT_OF_MEMORY);
break;
}
u32 dsize = dehrust(tmp, (u8*)mem_obj[hsrc].addr, ssize);
if (!dsize)
{
free(tmp);
set_status(ESP_ERR_INV_HST);
break;
}
void *dst = malloc_spiram(dsize);
if (!dst)
{
free(tmp);
set_status(ESP_ERR_OUT_OF_MEMORY);
break;
}
memcpy(dst, tmp, dsize);
free(tmp);
free(mem_obj[hsrc].addr);
mem_obj[hsrc].addr = dst;
mem_obj[hsrc].size = dsize;
mem_obj[hsrc].type = dtype;
wr_reg32(ESP_REG_DATA_OFFSET, 0);
wr_reg32(ESP_REG_DATA_SIZE, min(dsize, DMA_BUF_SIZE));
set_status(ESP_ST_READY);
}
break;
case TASK_UNZIP:
{
int hsrc = rd_reg8(ESP_REG_OBJ_HANDLE);
size_t ssize = rd_reg32(ESP_REG_DATA_SIZE);
int dtype = rd_reg8(ESP_REG_OBJ_TYPE);
if (!check_handle(hsrc))
{
set_status(ESP_ERR_INV_HST_HANDLE);
break;
}
if (mem_obj[hsrc].type != OBJ_TYPE_ZIP)
{
set_status(ESP_ERR_INV_OBJ_TYPE);
break;
}
auto src = (u8*)mem_obj[hsrc].addr;
size_t dsize = *(u32*)src;
void *dst = (u8*)malloc_spiram(dsize);
if (!dst)
{
set_status(ESP_ERR_OUT_OF_MEMORY);
break;
}
tinfl_init(decomp);
ssize -= 4;
tinfl_status decomp_status = tinfl_decompress(decomp, &src[4], &ssize, (u8*)dst, (u8*)dst, &dsize, TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
#ifdef VERBOSE
printf("Unzip rc = %d, inbytes = %d, outbytes = %d\r\n", decomp_status, ssize, dsize);
#endif
if (decomp_status != TINFL_STATUS_DONE)
{
set_status(ESP_ERR_INV_ZIP);
break;
}
mem_obj[hsrc].addr = dst;
mem_obj[hsrc].size = dsize;
mem_obj[hsrc].type = dtype;
wr_reg32(ESP_REG_DATA_OFFSET, 0);
wr_reg32(ESP_REG_DATA_SIZE, min(dsize, DMA_BUF_SIZE));
set_status(ESP_ST_READY);
}
break;
case TASK_RESET_STREAM_UNZIP:
process_rx_data(DREQ_ZIP, 0); // Reset stream depacker
break;
case TASK_XM_STREAM_LOAD:
xm_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
break;
case TASK_MOD_STREAM_LOAD:
mod_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
break;
case TASK_S3M_STREAM_LOAD:
s3m_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
break;
case TASK_TRACK_STREAM_BREAK:
xm_host_stream_abort_current();
set_status(ESP_ST_READY);
break;
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
case TASK_WSCAN:
{
if (net.is_busy)
{
set_status(ESP_ERR_NET_BUSY);
break;
}
net.is_busy = true;
esp_err_t wscan_err = (esp_err_t)wf_scan_locked(300 /* timeout */);
net.is_busy = false;
if (wscan_err != ESP_OK)
{
printf("E: WiFi scan failed: %s\r\n", esp_err_to_name(wscan_err));
set_status(ESP_ERR_INV_STATE);
break;
}
put_txq(DREQ_WSCAN);
}
break;
case TASK_CONN:
{
if (net.is_busy)
{
set_status(ESP_ERR_NET_BUSY);
break;
}
net.is_busy = true;
net.state = NETWORK_OPENING;
auto rc = wifi_connect_locked((const char*)net.ssid, (const char*)net.pwd, 10000);
net.is_busy = false;
net.state = rc ? NETWORK_OPEN : NETWORK_CLOSED;
if (rc)
{
get_ip(net.ip.own_ip, net.ip.mask, net.ip.gate);
set_status(ESP_ST_READY);
}
else
set_status(ESP_ERR_AP_NOT_CONNECTED);
}
break;
case TASK_DISCONN:
{
wifi_disconnect_now_locked();
net.state = NETWORK_CLOSED;
set_status(ESP_ST_READY);
}
break;
case TASK_HTTP_GET:
http_do_get();
break;
case TASK_WEATHER_GET:
weather_do_get();
break;
case TASK_HTTPS_GET:
https_do_get();
break;
case TASK_GOPHER_GET:
gopher_do_get();
break;
case TASK_HTTP_STREAM_START:
stream_http_start();
break;
case TASK_HTTPS_STREAM_START:
stream_https_start();
break;
case TASK_GOPHER_STREAM_START:
stream_gopher_start();
break;
case TASK_STREAM_READ:
stream_read();
break;
case TASK_STREAM_CLOSE:
stream_close();
break;
case TASK_HTTP_STREAM_READ:
http_stream_read_task();
break;
#else
case TASK_WSCAN:
case TASK_CONN:
case TASK_DISCONN:
case TASK_HTTP_GET:
case TASK_WEATHER_GET:
case TASK_HTTPS_GET:
case TASK_GOPHER_GET:
case TASK_HTTP_STREAM_START:
case TASK_HTTPS_STREAM_START:
case TASK_GOPHER_STREAM_START:
case TASK_STREAM_READ:
case TASK_STREAM_CLOSE:
case TASK_HTTP_STREAM_READ:
set_status(ESP_ERR_INV_COMMAND);
break;
#endif
case TASK_SFX_PLAY:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
if (!check_handle(handle) || mem_obj[handle].type != OBJ_TYPE_WAV)
{
set_status(ESP_ERR_INV_HANDLE);
break;
}
int channel = -1;
esp_err_t err = sfx_play_sync(handle, rd_reg8(ESP_REG_SFX_GROUP), SFX_VOLUME_MAX, SFX_PAN_CENTER, SFX_PITCH_ONE, &channel);
if (err != ESP_OK)
{
set_status(sfx_status_from_err(err));
break;
}
wr_reg8(ESP_REG_SFX_CHANNEL, channel);
set_status(ESP_ST_READY);
}
break;
case TASK_SFX_PLAY_EX:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
if (!check_handle(handle) || mem_obj[handle].type != OBJ_TYPE_WAV)
{
set_status(ESP_ERR_INV_HANDLE);
break;
}
u16 pitch = 0;
rd_regs(ESP_REG_SFX_PITCH, &pitch, sizeof(pitch));
int channel = -1;
esp_err_t err = sfx_play_sync(
handle,
rd_reg8(ESP_REG_SFX_GROUP),
rd_reg8(ESP_REG_SFX_VOLUME),
rd_reg8(ESP_REG_SFX_PAN),
pitch,
&channel);
if (err != ESP_OK)
{
set_status(sfx_status_from_err(err));
break;
}
wr_reg8(ESP_REG_SFX_CHANNEL, channel);
set_status(ESP_ST_READY);
}
break;
case TASK_SFX_STOP:
{
esp_err_t err = sfx_stop_sync(rd_reg8(ESP_REG_SFX_CHANNEL));
set_status(sfx_status_from_err(err));
}
break;
case TASK_SFX_SET_PARAMS:
{
u16 pitch = 0;
rd_regs(ESP_REG_SFX_PITCH, &pitch, sizeof(pitch));
esp_err_t err = sfx_set_params_sync(
rd_reg8(ESP_REG_SFX_CHANNEL),
rd_reg8(ESP_REG_SFX_VOLUME),
rd_reg8(ESP_REG_SFX_PAN),
pitch);
set_status(sfx_status_from_err(err));
}
break;
case TASK_SFX_SET_VOLUME:
{
esp_err_t err = sfx_set_volume_sync(rd_reg8(ESP_REG_SFX_VOLUME));
set_status(sfx_status_from_err(err));
}
break;
case TASK_SFX_GET_STATE:
{
SFX_CHANNEL_STATE state = {};
esp_err_t err = sfx_get_state_sync(rd_reg8(ESP_REG_SFX_CHANNEL), &state);
if (err != ESP_OK)
{
set_status(sfx_status_from_err(err));
break;
}
wr_reg8(ESP_REG_SFX_STATE_ACTIVE, state.active);
wr_reg8(ESP_REG_SFX_STATE_HANDLE, state.handle);
wr_reg8(ESP_REG_SFX_STATE_GROUP, state.group);
wr_reg8(ESP_REG_SFX_STATE_ORDER, state.group_order);
wr_reg8(ESP_REG_SFX_STATE_VOLUME, state.volume);
wr_reg8(ESP_REG_SFX_STATE_PAN, state.pan);
wr_regs(ESP_REG_SFX_STATE_PITCH, &state.pitch, sizeof(state.pitch));
wr_reg32(ESP_REG_SFX_STATE_RATE, state.sample_rate);
wr_reg32(ESP_REG_SFX_STATE_FRAMES, state.frame_count);
wr_reg32(ESP_REG_SFX_STATE_POSITION, state.position);
wr_regs(ESP_REG_SFX_STATE_BITS, &state.bits_per_sample, sizeof(state.bits_per_sample));
wr_reg8(ESP_REG_SFX_STATE_CHANNELS, state.channels);
wr_reg8(ESP_REG_SFX_STATE_SIGNED, state.is_signed);
set_status(ESP_ST_READY);
}
break;
case TASK_TRACK_GET_STATE:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_XMC && mem_obj[handle].type != OBJ_TYPE_MDC && mem_obj[handle].type != OBJ_TYPE_S3C))
{
set_status(ESP_ERR_INV_XM_HANDLE);
break;
}
wr_reg8(ESP_REG_TRACK_STATUS, mem_obj[handle].state);
wr_reg8(ESP_REG_TRACK_IS_CURRENT, curr_xm_handle == handle);
set_status(ESP_ST_READY);
}
break;
case TASK_TRACK_GET_INFO:
{
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
if (!check_handle(handle) || !mem_obj[handle].addr || (mem_obj[handle].type != OBJ_TYPE_XMC && mem_obj[handle].type != OBJ_TYPE_MDC && mem_obj[handle].type != OBJ_TYPE_S3C))
{
set_status(ESP_ERR_INV_XM_HANDLE);
break;
}
xm_context_t *ctx = (xm_context_t*)mem_obj[handle].addr;
uint8_t pattern_index = 0, pattern = 0, row = 0;
uint64_t samples = 0;
uint16_t bpm = 0, tempo = 0;
xm_get_position(ctx, &pattern_index, &pattern, &row, &samples);
xm_get_playing_speed(ctx, &bpm, &tempo);
wr_reg8(ESP_REG_TRACK_STATUS, mem_obj[handle].state);
wr_reg8(ESP_REG_TRACK_IS_CURRENT, curr_xm_handle == handle);
wr_reg8(ESP_REG_TRACK_PATTERN_INDEX, pattern_index);
wr_reg8(ESP_REG_TRACK_PATTERN, pattern);
wr_reg8(ESP_REG_TRACK_ROW, row);
wr_reg32(ESP_REG_TRACK_SAMPLES, (u32)samples);
wr_regs(ESP_REG_TRACK_BPM, &bpm, sizeof(bpm));
wr_regs(ESP_REG_TRACK_TEMPO, &tempo, sizeof(tempo));
wr_reg8(ESP_REG_TRACK_LOOP_COUNT, xm_get_loop_count(ctx));
wr_reg32(ESP_REG_TRACK_TOTAL_SAMPLES, xm_get_total_samples(handle));
set_status(ESP_ST_READY);
}
break;
case TASK_SFX_STOP_GROUP:
{
esp_err_t err = sfx_stop_group_sync(rd_reg8(ESP_REG_SFX_GROUP));
set_status(sfx_status_from_err(err));
}
break;
case TASK_REBOOT:
ESP_LOGE(TAG, "software restart requested by SPI command REBOOT (0xED)");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
break;
case TASK_RESET:
ESP_LOGE(TAG, "software restart requested by SPI command RESET (0xEE)");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
break;
case TASK_VFS_SERVER_START:
vfs_bridge_start_server();
set_status(ESP_ST_READY);
break;
case TASK_VFS_SERVER_STOP:
vfs_bridge_stop_server();
set_status(ESP_ST_READY);
break;
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
#pragma once
enum
{
TASK_MAKE_OBJ,
TASK_DEL_OBJ,
TASK_KILL_OBJ,
TASK_LOAD_ELF,
TASK_LOAD_ELF_OPT,
TASK_RUN_FUNC0,
TASK_RUN_FUNC1,
TASK_RUN_FUNC2,
TASK_RUN_FUNC3,
TASK_DEHST,
TASK_UNZIP,
TASK_RESET_STREAM_UNZIP,
TASK_XM_STREAM_LOAD,
TASK_MOD_STREAM_LOAD,
TASK_S3M_STREAM_LOAD,
TASK_TRACK_STREAM_BREAK,
TASK_TRACK_GET_STATE,
TASK_TRACK_GET_INFO,
TASK_WSCAN,
TASK_CONN,
TASK_DISCONN,
TASK_HTTP_GET,
TASK_WEATHER_GET,
TASK_HTTPS_GET,
TASK_GOPHER_GET,
TASK_HTTP_STREAM_START,
TASK_HTTPS_STREAM_START,
TASK_GOPHER_STREAM_START,
TASK_STREAM_READ,
TASK_STREAM_CLOSE,
TASK_HTTP_STREAM_READ,
TASK_SFX_PLAY,
TASK_SFX_PLAY_EX,
TASK_SFX_STOP,
TASK_SFX_SET_PARAMS,
TASK_SFX_SET_VOLUME,
TASK_SFX_GET_STATE,
TASK_SFX_STOP_GROUP,
TASK_REBOOT,
TASK_RESET,
TASK_VFS_SERVER_START,
TASK_VFS_SERVER_STOP,
};
#pragma pack(1)
struct NET
{
bool is_init = false;
bool is_busy = false;
u8 state;
u8 ssid[33];
u8 pwd[62];
u8 *url;
struct
{
u8 ip[4];
u8 own_ip[4];
u8 mask[4];
u8 gate[4];
} ip;
};
#pragma pack()
extern QueueHandle_t helper_queue;
extern struct NET net;
void put_txq(int type);
void put_txq_isr(int type);
void put_rxq(int type);
void put_rxq_isr(int type);
void put_helper_isr(int task);
void helper_task(void *arg);
u32 prepare_tx_data(u8 type, size_t size);
void process_rx_data(u8 type, size_t size);
+184
View File
@@ -0,0 +1,184 @@
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_heap_caps.h"
#include "esp_crt_bundle.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "mem_obj.h"
#include "helper.h"
#include "http_client.h"
const char *TAG = "http";
#define HTTP_BUF_SIZE (2 * 1024 * 1024)
void http_init() {}
void http_do_get()
{
uint8_t *http_buf = NULL;
uint8_t url_handle = rd_reg8(ESP_REG_OBJ_HANDLE);
char *url;
int total = 0;
int len;
int code;
esp_http_client_handle_t client;
esp_http_client_config_t cfg = {};
esp_err_t err;
int resp;
ESP_LOGI(TAG, "HTTP_GET handle=%d", url_handle);
http_buf = (uint8_t*)heap_caps_malloc(HTTP_BUF_SIZE, MALLOC_CAP_SPIRAM);
if (!http_buf)
{
set_status(0xA0);
ESP_LOGE(TAG, "HTTP buf malloc error");
return;
}
if (!net.url[0])
{
ESP_LOGW(TAG, "URL not set");
set_status(0x81);
return;
}
url = (char*)net.url;
if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0)
{
ESP_LOGW(TAG, "invalid URL format");
set_status(0x81);
return;
}
ESP_LOGI(TAG, "GET %s", url);
cfg.url = url;
cfg.timeout_ms = 30000;
cfg.buffer_size = 4096;
cfg.user_agent = "ZiFi/1.0";
// Enable TLS for HTTPS
if (strncmp(url, "https://", 8) == 0)
cfg.crt_bundle_attach = esp_crt_bundle_attach;
client = esp_http_client_init(&cfg);
if (!client)
{
ESP_LOGW(TAG, "init fail");
set_status(0xB2);
return;
}
err = esp_http_client_open(client, 0);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "open fail");
esp_http_client_cleanup(client);
set_status(0xB2);
goto cleanup;
}
esp_http_client_fetch_headers(client);
code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "status %d", code);
if (code < 200 || code >= 300)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
set_status(0xB3);
goto cleanup;
}
for (;;)
{
len = esp_http_client_read(client, (char*)http_buf + total, HTTP_BUF_SIZE - total);
if (len > 0)
{
total += len;
wr_reg32(ESP_REG_DATA_SIZE, total);
ESP_LOGI(TAG, "got: %d bytes, progress: %d bytes", len, total);
if (total >= HTTP_BUF_SIZE) break;
}
else if (len == -ESP_ERR_HTTP_EAGAIN)
continue;
else if (len == 0)
{
ESP_LOGI(TAG, "empty response received");
break;
}
else
{
ESP_LOGE(TAG, "error: %d", -len);
break;
}
vTaskDelay(1);
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGI(TAG, "recv %d bytes", total);
if (total == 0)
{
ESP_LOGW(TAG, "empty response");
set_status(0xB4);
goto cleanup;
}
resp = make_obj(total, OBJ_TYPE_DATA);
if (resp < 0)
{
ESP_LOGE(TAG, "make_obj failed");
set_status(0xA0);
goto cleanup;
}
memcpy(mem_obj[resp].addr, http_buf, total);
wr_reg8(ESP_REG_OBJ_HANDLE, resp);
wr_reg32(ESP_REG_DATA_SIZE, total);
set_status(ESP_ST_READY);
ESP_LOGI(TAG, "done: handle=%d size=%d", resp, total);
cleanup:
if (http_buf) free(http_buf);
}
void https_do_get()
{
// HTTPS uses same logic as HTTP - esp_http_client handles both
// Just need to ensure URL starts with https://
http_do_get();
}
void http_stream_read_task() {}
void http_stream_delete(uint8_t handle) {}
bool http_is_stream_handle(uint8_t handle) { return false; }
void http_stream_read(uint8_t handle, uint32_t offset, uint32_t size) {}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
void http_init(void);
void http_do_get(void);
void https_do_get(void);
// Streaming API
void http_stream_read_task(void); // Вызывается из helper task
void http_stream_delete(uint8_t handle);
bool http_is_stream_handle(uint8_t handle);
void http_stream_read(uint8_t handle, uint32_t offset, uint32_t size); // legacy
+18
View File
@@ -0,0 +1,18 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/usb_host_hid: ^1.1.0
espressif/esp_new_jpeg: ^1.0.0
+238
View File
@@ -0,0 +1,238 @@
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_err.h"
#include "bootloader_random.h"
#include "esp_random.h"
#include "nvs.h"
#include "miniz.h"
#include "nvs_flash.h"
#include <esp_crc.h>
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "console.h"
#include "nvs_params.h"
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
#include "wifi.h"
#include "http_client.h"
#include "gopher_client.h"
#include "stream_client.h"
#include "at_uart.h"
#include "at_cip.h"
#include "vfs_bridge.h"
#include "vfs_request.h"
#endif
#include "mem_obj.h"
#include "tracker.h"
#include "stats.h"
#include "ft8xx.h"
#include "elf.h"
#include "sdmmc.h"
#include "helper.h"
#include "depack.h"
#include "ps2_mouse.h"
#include "usb_mouse.h"
// #define ISR_PRINTF
EXT_RAM_BSS_ATTR tinfl_decompressor decomp_buf;
tinfl_decompressor *decomp = &decomp_buf;
const char TAG[] = "main";
int usb_mouse_start_on_boot = 0;
u32 task_ram_type_critical = MALLOC_CAP_INTERNAL;
u32 task_ram_type_non_critical = MALLOC_CAP_SPIRAM;
// ------------- Debug helpers
size_t sram_used_prev;
void log_sram_used(const char *name)
{
size_t total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
size_t free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t used = total - free;
int delta = (int)used - (int)sram_used_prev;
ESP_LOGI("SRAM", "%s, SRAM used: %d bytes", name, delta);
sram_used_prev = used;
}
#ifdef ISR_PRINTF
#define PRINT_QUEUE_LENGTH 100
static QueueHandle_t printQueue;
void print_task(void *pvParameters)
{
char *message;
while (1)
{
if (xQueueReceive(printQueue, &message, portMAX_DELAY))
printf("%s", message);
}
}
extern "C"
{
void print_isr(const char* msg)
{
xQueueSendFromISR(printQueue, &msg, NULL);
}
}
#endif // ISR_PRINTF
// void IRAM_ATTR splash(int n)
// {
// while (n--)
// {
// gpio_set_level((gpio_num_t)GPIO_TEST1, 1); gpio_set_level((gpio_num_t)GPIO_TEST1, 0); // !!!
// }
// }
// ------------- System start and initialization
extern "C" void app_main()
{
esp_err_t err;
// ----- Test GPIO init
// gpio_set_direction((gpio_num_t)GPIO_TEST1, GPIO_MODE_OUTPUT);
// gpio_set_direction((gpio_num_t)GPIO_TEST2, GPIO_MODE_OUTPUT);
// gpio_set_direction((gpio_num_t)GPIO_TEST3, GPIO_MODE_OUTPUT);
sram_used_prev = heap_caps_get_total_size(MALLOC_CAP_INTERNAL) - heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
// ----- Runtime inits
stats::init();
bootloader_random_enable();
#ifdef ISR_PRINTF
// ----- ISR printf init
printQueue = xQueueCreate(PRINT_QUEUE_LENGTH, sizeof(char*));
log_sram_used("QueueCreate printQueue");
xTaskCreatePinnedToCoreWithCaps(print_task, "isr-printf", 2048, NULL, 1, NULL, 0, task_ram_type_non_critical);
log_sram_used("TaskCreate print_task");
#endif
// ----- NVS + params init
initialize_nvs();
ESP_ERROR_CHECK(app_params_load());
usb_mouse_start_on_boot = app_params.usb_mode ? 1 : 0;
ESP_LOGI("MAIN", "usb_mode=%u, usb_mouse_start_on_boot=%d", app_params.usb_mode, usb_mouse_start_on_boot);
// ----- Wi-Fi init
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
net.is_init = false;
net.state = NETWORK_CLOSED;
if (wifi_is_enabled())
{
initialize_wifi();
wifi_start_autoconnect();
net.is_init = true;
log_sram_used("initialize_wifi");
}
else
ESP_LOGI("MAIN", "WiFi disabled by config");
#endif
// ----- Helper init
helper_queue = xQueueCreateWithCaps(2, sizeof(int), task_ram_type_non_critical);
log_sram_used("QueueCreate helper_queue");
xTaskCreatePinnedToCoreWithCaps(helper_task, "helper", 6144, NULL, HELPER_TASK_PRIO, NULL, 0, task_ram_type_critical);
log_sram_used("TaskCreate helper_task");
// ----- LibXM init
initialize_xm();
// ----- Network client init
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
http_init();
log_sram_used("http_init");
gopher_init();
log_sram_used("gopher_init");
stream_init();
log_sram_used("stream_init");
#endif
// ----- SDMMC init
#if defined(CONFIG_IDF_TARGET_ESP32P4)
sd_ldo_init();
#endif
// ----- SPI slave init
init_slave_hd();
// ----- FT812 init
init_ft8xx();
log_sram_used("init_ft8xx");
// ----- PS/2 mouse init
err = ps2_mouse_start();
if (err != ESP_OK)
ESP_LOGE("PS2", "ps2_mouse_start failed: %s", esp_err_to_name(err));
log_sram_used("PS/2 mouse init");
// ----- USB mouse init
if (usb_mouse_start_on_boot)
{
err = usb_mouse_start();
if (err != ESP_OK)
ESP_LOGE("USBM", "usb_mouse_start failed: %s", esp_err_to_name(err));
}
log_sram_used("USB mouse init");
// ----- Console init
initialize_console();
xTaskCreatePinnedToCoreWithCaps(console_task, "console", 6144, NULL, CONSOLE_TASK_PRIO, NULL, 0, task_ram_type_critical);
log_sram_used("TaskCreate console_task");
// ----- AT command UART init
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
at_cip_init();
at_uart_init();
// Stack sizes sized for AT+CIPSTART="SSL": esp_tls_conn_new_sync()'s DNS lookup
// (getaddrinfo -> lwip_getaddrinfo -> netconn_gethostbyname_addrtype_n -> ...)
// is a much deeper call chain than the plain-TCP path's gethostbyname(), and
// mbedtls's own handshake/record processing adds further stack depth on top -
// the original 4096/3072 sizes (fine for plain TCP/UDP) overflowed and crashed
// with a seemingly unrelated LoadProhibited panic inside a spinlock deep in
// that chain, confirmed via backtrace against esp-spi.elf.
xTaskCreatePinnedToCoreWithCaps(at_task, "at-cmd", 12288, NULL, AT_TASK_PRIO, &g_at_task_handle, 1, task_ram_type_critical);
xTaskCreatePinnedToCoreWithCaps(at_cip_rx_task, "at-cip-rx", 8192, NULL, AT_TASK_PRIO, &g_at_cip_rx_task_handle, 1, task_ram_type_critical);
log_sram_used("TaskCreate at_task");
#endif
// ----- SMB2/3 network share (Phase 1 fake filesystem + Phase 2 SPI bridge to the Z80, see project plan)
// The task is created here unconditionally (cheap - it just registers
// console commands/queues and then blocks), but it does NOT start
// listening on its own anymore: it sits idle until ESP_CMD_VFS_SERVER_START
// (see vfs_bridge_start_server()/TASK_VFS_SERVER_START in helper.cpp) -
// the smbvfs.wmf plugin explicitly starts/stops the server (and the other
// services it pauses meanwhile) instead of it riding along with WiFi.
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
vfs_request_init();
vfs_bridge_init();
xTaskCreatePinnedToCoreWithCaps(smb_server_task, "smb-server", 8192, NULL, SMB_TASK_PRIO, NULL, 1, task_ram_type_critical);
log_sram_used("TaskCreate smb_server_task");
#endif
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <stdint.h>
#include "miniz.h"
#include "tracker.h"
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
extern u32 task_ram_type_critical;
extern u32 task_ram_type_non_critical;
enum
{
// Core 0
CONSOLE_TASK_PRIO = 2,
WIFI_AUTOCONNECT_TASK_PRIO = 3,
STREAMER_TASK_PRIO = 4,
TCPIP_TASK_PRIO = 5,
WIFI_TASK_PRIO = 6,
SPI_BG_TASK_PRIO = 7,
TRACKER_HELPER_TASK_PRIO = 8,
HELPER_TASK_PRIO = 9,
USB_MOUSE_TASK_PRIO = 10,
USB_MOUSE_LIB_TASK_PRIO = 10,
USB_MOUSE_HID_TASK_PRIO = 11,
SLAVE_TASK_PRIO = 14,
// Core 1
TRACKER_PLAYER_TASK_PRIO = 10,
I2S_TASK_PRIO = 12,
AT_TASK_PRIO = 4,
SMB_TASK_PRIO = 4,
};
#define max(x, y) (((x) > (y)) ? (x) : (y))
#define min(x, y) (((x) < (y)) ? (x) : (y))
extern tinfl_decompressor *decomp;
// #define VERBOSE
#define _delay_ms(a) vTaskDelay(pdMS_TO_TICKS(a));
#ifdef CONFIG_SPIRAM
#define malloc_spiram(a) heap_caps_malloc(a, MALLOC_CAP_SPIRAM)
#else
#define malloc_spiram(a) malloc(a)
#endif
void log_sram_used(const char *name);
+231
View File
@@ -0,0 +1,231 @@
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "helper.h"
#include "mem_obj.h"
#include "tracker.h"
#include "sfx.h"
const char TAG[] = "mem_obj";
EXT_RAM_BSS_ATTR MEM_OBJ mem_obj[OBJ_HANDLES_MAX] = {};
int find_avail_handle()
{
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
if (!mem_obj[i].type)
return i;
return -1;
}
int check_handle(int h)
{
return mem_obj[h].type != 0;
}
int mem_obj_can_flat_rw(int h)
{
if (!check_handle(h)) return 0;
switch (mem_obj[h].type)
{
case OBJ_TYPE_LIB:
case OBJ_TYPE_XMC:
case OBJ_TYPE_MDC:
case OBJ_TYPE_S3C:
return 0;
}
return 1;
}
int delete_obj(int h)
{
switch (mem_obj[h].type)
{
case OBJ_TYPE_LIB:
{
if (mem_obj[h].text) free(mem_obj[h].text);
if (mem_obj[h].data) free(mem_obj[h].data);
if (mem_obj[h].rodata) free(mem_obj[h].rodata);
if (mem_obj[h].bss) free(mem_obj[h].bss);
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
return 1;
}
case OBJ_TYPE_XMC:
case OBJ_TYPE_MDC:
case OBJ_TYPE_S3C:
if (mem_obj[h].state == TRACK_OBJ_ST_PLAYING)
{
ESP_LOGE(TAG, "Cannot delete playing tracker object: handle %02X", h);
return 0;
}
if (mem_obj[h].addr)
{
xm_free_context((xm_context_t*)mem_obj[h].addr);
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
return 1;
}
break;
case OBJ_TYPE_WAV:
{
u8 old_state = mem_obj[h].state;
mem_obj[h].state = OBJ_ST_DELETING;
esp_err_t err = sfx_stop_handle_sync(h);
if (err != ESP_OK)
{
mem_obj[h].state = old_state;
ESP_LOGE(TAG, "Cannot stop WAV object users before delete: handle %02X, err %s", h, esp_err_to_name(err));
return 0;
}
}
break;
}
if (mem_obj[h].addr) free(mem_obj[h].addr);
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
return 1;
}
void write_obj(int handle, const void *from, int size)
{
memcpy(mem_obj[handle].addr, from, max(mem_obj[handle].size, size));
}
int attach_obj(void *addr, int obj_size, int obj_type)
{
if (!addr || obj_size <= 0)
{
set_status(ESP_ERR_INV_PARAM);
return -1;
}
int handle = find_avail_handle();
if (handle == -1)
{
set_status(ESP_ERR_OUT_OF_HANDLES);
ESP_LOGE(TAG, "Cannot allocate handle for an object!");
return -1;
}
switch (obj_type)
{
case OBJ_TYPE_XM:
case OBJ_TYPE_XMC:
case OBJ_TYPE_MOD:
case OBJ_TYPE_MDC:
case OBJ_TYPE_S3M:
case OBJ_TYPE_S3C:
case OBJ_TYPE_WAV:
case OBJ_TYPE_DATA:
case OBJ_TYPE_ELF:
case OBJ_TYPE_HST:
case OBJ_TYPE_ZIP:
case OBJ_TYPE_DATAF:
break;
default:
ESP_LOGE(TAG, "Cannot attach unknown type object!");
set_status(ESP_ERR_INV_OBJ_TYPE);
return -1;
}
if (obj_type == OBJ_TYPE_XMC || obj_type == OBJ_TYPE_MDC || obj_type == OBJ_TYPE_S3C)
{
xm_context_t *ctx = (xm_context_t*)addr;
if (!tracker_context_has_registered_segments(ctx))
{
size_t context_size = ctx->ctx_size ? ctx->ctx_size : (size_t)obj_size;
if (!ctx->ctx_size) ctx->ctx_size = context_size;
if (!tracker_context_register_segment(ctx, addr, context_size, TRACKER_CONTEXT_SEG_CONTEXT))
{
set_status(ESP_ERR_INV_STATE);
ESP_LOGE(TAG, "Cannot attach tracker object without segment ownership!");
return -1;
}
}
}
mem_obj[handle].addr = addr;
mem_obj[handle].size = obj_size;
mem_obj[handle].type = obj_type;
mem_obj[handle].state = OBJ_ST_NONE;
return handle;
}
int make_obj(int obj_size, int obj_type)
{
void *obj_addr = NULL;
int handle = find_avail_handle();
if (handle == -1)
{
set_status(ESP_ERR_OUT_OF_HANDLES);
ESP_LOGE(TAG, "Cannot allocate handle for an object!");
return -1;
}
switch (obj_type)
{
case OBJ_TYPE_XM:
case OBJ_TYPE_MOD:
case OBJ_TYPE_S3M:
case OBJ_TYPE_WAV:
case OBJ_TYPE_DATA:
case OBJ_TYPE_ELF:
case OBJ_TYPE_HST:
case OBJ_TYPE_ZIP:
obj_addr = malloc_spiram(obj_size);
break;
case OBJ_TYPE_DATAF:
obj_addr = heap_caps_malloc(obj_size, MALLOC_CAP_INTERNAL);
break;
default:
{
ESP_LOGE(TAG, "Cannot create unknown type object!");
set_status(ESP_ERR_INV_OBJ_TYPE);
return -1;
}
}
if (!obj_addr)
{
set_status(ESP_ERR_OUT_OF_MEMORY);
ESP_LOGE(TAG, "Cannot allocate memory for an object! (%d bytes)", obj_size);
return -1;
}
#ifdef VERBOSE
else
printf("Object created: type %02X, handle %02X, addr %08X, size %u\r\n", obj_type, handle, (unsigned int)obj_addr, obj_size);
#endif
mem_obj[handle].addr = obj_addr;
mem_obj[handle].size = obj_size;
mem_obj[handle].type = obj_type;
mem_obj[handle].state = OBJ_ST_NONE;
return handle;
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#pragma pack(1)
typedef struct
{
// Common
void *addr;
size_t size;
u8 type;
u8 state;
// Lib
void *text;
void *rodata;
void *data;
void *bss;
u32 sz_text;
u32 sz_rodata;
u32 sz_data;
u32 sz_bss;
void *entry;
} MEM_OBJ;
#pragma pack()
#define OBJ_HANDLES_MAX 256
extern MEM_OBJ mem_obj[OBJ_HANDLES_MAX];
int find_avail_handle();
int check_handle(int h);
int delete_obj(int h);
int make_obj(int obj_size, int obj_type);
int attach_obj(void *addr, int obj_size, int obj_type);
int mem_obj_can_flat_rw(int h);
void write_obj(int handle, const void *from, int size);
+466
View File
@@ -0,0 +1,466 @@
// See named_pipe_rpc.h. Minimal MS-RPCE (bind/bind_ack/request/response/
// fault) plus just enough MS-WKST NetrWkstaGetInfo and MS-SRVS
// NetrShareGetInfo/NetrServerGetInfo to answer what Windows Explorer asks
// before it will browse a share. Field layouts follow the standard NDR
// wire rules (conformant/varying strings, deferred pointer data,
// embedded-union encoding). A packet capture confirmed Explorer really
// does require a working reply here (clean STATUS_NOT_IMPLEMENTED
// rejections just get retried forever) and confirmed which two pipes/
// opnums are being probed - but this specific bind/response encoding is
// new and not yet verified against a real exchange; the opnum logging
// below is there to check that on the next capture.
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "named_pipe_rpc.h"
static const char *TAG = "rpc_pipe";
// ---- DCE/RPC wire constants -----------------------------------------
#define RPC_PTYPE_REQUEST 0
#define RPC_PTYPE_RESPONSE 2
#define RPC_PTYPE_FAULT 3
#define RPC_PTYPE_BIND 11
#define RPC_PTYPE_BIND_ACK 12
#define RPC_PTYPE_BIND_NAK 13
#define PFC_FIRST_FRAG 0x01
#define PFC_LAST_FRAG 0x02
// UUID 8a885d04-1ceb-11c9-9fe8-08002b104860 v2.0 - the classic NDR transfer
// syntax essentially every DCE/RPC client/server supports.
static const uint8_t NDR_TRANSFER_SYNTAX_UUID[16] = {
0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11,
0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60
};
#define NDR_TRANSFER_SYNTAX_VERSION 2
// ---- tiny append-only byte buffer for building replies ---------------
#define RPC_BUF_MAX 512
struct rpc_buf
{
uint8_t data[RPC_BUF_MAX];
uint32_t len;
};
static void buf_reset(rpc_buf *b) { b->len = 0; }
static void buf_put(rpc_buf *b, const void *src, uint32_t n)
{
if (b->len + n > RPC_BUF_MAX) n = RPC_BUF_MAX - b->len;
memcpy(b->data + b->len, src, n);
b->len += n;
}
static void buf_u8(rpc_buf *b, uint8_t v) { buf_put(b, &v, 1); }
static void buf_u16(rpc_buf *b, uint16_t v) { buf_put(b, &v, 2); }
static void buf_u32(rpc_buf *b, uint32_t v) { buf_put(b, &v, 4); }
static void buf_align4(rpc_buf *b)
{
while (b->len % 4) buf_u8(b, 0);
}
// NDR conformant+varying WCHAR string: max_count, offset(=0), actual_count
// (all in WCHARs, including the NUL terminator), then the UTF-16LE bytes,
// padded to a 4-byte boundary.
static void buf_ndr_string(rpc_buf *b, const char *s)
{
uint32_t n = (uint32_t)strlen(s) + 1; // include NUL
buf_u32(b, n); // max_count
buf_u32(b, 0); // offset
buf_u32(b, n); // actual_count
for (uint32_t i = 0; i < n; i++)
{
buf_u16(b, (uint8_t)s[i]); // ASCII-safe names only, widened to UTF-16LE
}
buf_align4(b);
}
// ---- per-handle pipe state --------------------------------------------
#define MAX_RPC_PIPES 4
struct rpc_pipe_state
{
bool in_use;
smb2_file_id file_id;
rpc_pipe_type type;
uint32_t call_id;
rpc_buf reply;
bool reply_pending;
struct smb2_context *owner;
};
static rpc_pipe_state s_pipes[MAX_RPC_PIPES];
static rpc_pipe_state *find_pipe(const smb2_file_id file_id)
{
for (int i = 0; i < MAX_RPC_PIPES; i++)
if (s_pipes[i].in_use && !memcmp(s_pipes[i].file_id, file_id, sizeof(smb2_file_id)))
return &s_pipes[i];
return NULL;
}
rpc_pipe_type rpc_pipe_type_for_name(const char *name)
{
if (!name) return RPC_PIPE_NONE;
if (!strcmp(name, "wkssvc")) return RPC_PIPE_WKSSVC;
if (!strcmp(name, "srvsvc")) return RPC_PIPE_SRVSVC;
return RPC_PIPE_NONE;
}
void rpc_pipe_open(const smb2_file_id file_id, rpc_pipe_type type, struct smb2_context *owner)
{
rpc_pipe_state *slot = find_pipe(file_id);
if (!slot)
{
for (int i = 0; i < MAX_RPC_PIPES; i++)
if (!s_pipes[i].in_use) { slot = &s_pipes[i]; break; }
}
if (!slot)
{
ESP_LOGW(TAG, "rpc_pipe_open: no free slot");
return;
}
memset(slot, 0, sizeof(*slot));
slot->in_use = true;
memcpy(slot->file_id, file_id, sizeof(smb2_file_id));
slot->type = type;
slot->owner = owner;
}
void rpc_pipe_close(const smb2_file_id file_id)
{
rpc_pipe_state *slot = find_pipe(file_id);
if (slot) slot->in_use = false;
}
bool rpc_pipe_is_open(const smb2_file_id file_id)
{
return find_pipe(file_id) != NULL;
}
void rpc_pipe_reset_for_owner(struct smb2_context *owner)
{
for (int i = 0; i < MAX_RPC_PIPES; i++)
if (s_pipes[i].in_use && s_pipes[i].owner == owner)
s_pipes[i].in_use = false;
}
// ---- DCE/RPC common header helpers ------------------------------------
static void put_rpc_header(rpc_buf *b, uint8_t ptype, uint32_t call_id)
{
buf_u8(b, 5); // rpc_vers
buf_u8(b, 0); // rpc_vers_minor
buf_u8(b, ptype);
buf_u8(b, PFC_FIRST_FRAG | PFC_LAST_FRAG);
buf_u8(b, 0x10); buf_u8(b, 0x00); buf_u8(b, 0x00); buf_u8(b, 0x00); // drep: little-endian/ASCII/IEEE
buf_u16(b, 0); // frag_length - patched in fixup_frag_length()
buf_u16(b, 0); // auth_length
buf_u32(b, call_id);
}
static void fixup_frag_length(rpc_buf *b)
{
uint16_t len = (uint16_t)b->len;
memcpy(b->data + 8, &len, 2); // frag_length is at header offset 8
}
// ---- BIND handling ------------------------------------------------------
// Parses one client bind PDU (after the 16-byte common header) and builds
// a bind_ack accepting the first context whose offered transfer syntax
// matches classic NDR, rejecting (per-context) any others - modern Windows
// clients routinely offer NDR64 and a "bind time feature negotiation"
// pseudo-context alongside classic NDR in the same bind.
static void handle_bind(rpc_pipe_state *pipe, const uint8_t *body, uint32_t body_len)
{
rpc_buf *out = &pipe->reply;
buf_reset(out);
put_rpc_header(out, RPC_PTYPE_BIND_ACK, pipe->call_id);
if (body_len < 12) { fixup_frag_length(out); pipe->reply_pending = true; return; }
uint16_t max_xmit; memcpy(&max_xmit, body + 0, 2);
uint16_t max_recv; memcpy(&max_recv, body + 2, 2);
uint8_t n_context = body[8];
buf_u16(out, max_xmit);
buf_u16(out, max_recv);
buf_u32(out, 0x12345678); // assoc_group_id - arbitrary nonzero
// Secondary address string: a real Windows server sends "\PIPE\<name>\0"
// here (confirmed via packet capture); we sent an empty string before.
const char *sec_addr = (pipe->type == RPC_PIPE_SRVSVC) ? "\\PIPE\\srvsvc" : "\\PIPE\\wkssvc";
uint16_t sec_addr_len = (uint16_t)strlen(sec_addr) + 1; // include NUL
buf_u16(out, sec_addr_len);
buf_put(out, sec_addr, sec_addr_len);
buf_align4(out); // realign before results array
if (n_context > 32) n_context = 0; // sanity guard
buf_u8(out, n_context);
buf_u8(out, 0); buf_u8(out, 0); buf_u8(out, 0); // pad
uint32_t off = 12;
for (uint8_t c = 0; c < n_context; c++)
{
if (off + 4 > body_len) break;
uint8_t n_transfer = body[off + 2];
// p_cont_id(2) + n_transfer_syn(1) + reserved(1) + abstract uuid(16) + abstract version(4)
uint32_t ts_base = off + 4 + 16 + 4;
bool accepted = false;
for (uint8_t t = 0; t < n_transfer; t++)
{
uint32_t ts_off = ts_base + (uint32_t)t * 20;
if (ts_off + 16 > body_len) break;
if (!memcmp(body + ts_off, NDR_TRANSFER_SYNTAX_UUID, 16))
{
accepted = true;
break;
}
}
if (accepted)
{
buf_u16(out, 0); // ack_result: acceptance
buf_u16(out, 0); // ack_reason
buf_put(out, NDR_TRANSFER_SYNTAX_UUID, 16);
buf_u32(out, NDR_TRANSFER_SYNTAX_VERSION);
}
else
{
buf_u16(out, 2); // ack_result: provider_rejection
buf_u16(out, 2); // ack_reason: transfer syntaxes not supported
uint8_t zero_uuid[16] = {0};
buf_put(out, zero_uuid, 16);
buf_u32(out, 0);
}
// Advance past every transfer syntax offered for this context (not
// just however many we scanned before matching/giving up), so the
// next context element is found at the right offset.
off = ts_base + (uint32_t)n_transfer * 20;
}
fixup_frag_length(out);
pipe->reply_pending = true;
}
// ---- WKSSVC: NetrWkstaGetInfo (opnum 0) --------------------------------
static void wkssvc_dispatch(rpc_pipe_state *pipe, uint16_t opnum, uint16_t context_id,
const uint8_t *stub, uint32_t stub_len)
{
rpc_buf *out = &pipe->reply;
buf_reset(out);
if (opnum != 0)
{
put_rpc_header(out, RPC_PTYPE_FAULT, pipe->call_id);
buf_u32(out, 0); buf_u16(out, context_id); buf_u8(out, 0); buf_u8(out, 0);
buf_u32(out, 0x1c010002); // nca_s_op_rng_error
fixup_frag_length(out);
pipe->reply_pending = true;
return;
}
put_rpc_header(out, RPC_PTYPE_RESPONSE, pipe->call_id);
buf_u32(out, 0); // alloc_hint - patched below
buf_u16(out, context_id);
buf_u8(out, 0); buf_u8(out, 0); // cancel_count, reserved
uint32_t stub_start = out->len;
// WkstaInfo is a top-level [out, switch_is(Level)] pointer parameter - a
// ref pointer by default, which NDR does NOT prefix with a referent ID
// (only unique/embedded pointers get one). A stray extra referent here
// shifted every field after it by 4 bytes for any strict NDR client -
// confirmed via Wireshark's own MS-SRVS/MS-WKST dissector misreading our
// "referent" value as the Level field ("Level: 262144" = 0x00040000, our
// old srvsvc referent) and flagging the rest of the packet malformed.
// Real Windows Explorer's RPC runtime is equally strict; only Samba's
// permissive smbclient tolerated this, which is why Day-1 testing looked
// fine. The union's own switch value is the first real stub byte.
buf_u32(out, 100); // encapsulated union switch: level 100
buf_u32(out, 500); // wki100_platform_id = PLATFORM_ID_NT
buf_u32(out, 0x00020004); // referent: computer_name
buf_u32(out, 0x00020008); // referent: langroup
buf_u32(out, 10); // wki100_ver_major
buf_u32(out, 0); // wki100_ver_minor
buf_ndr_string(out, "ZXEVO"); // deferred: computer_name
buf_ndr_string(out, "WORKGROUP"); // deferred: langroup
buf_u32(out, 0); // NET_API_STATUS return: NERR_Success
uint32_t stub_len_out = out->len - stub_start;
memcpy(out->data + 16, &stub_len_out, 4); // alloc_hint field, right after the 16-byte common header
fixup_frag_length(out);
pipe->reply_pending = true;
(void)stub; (void)stub_len;
}
// ---- SRVSVC: NetrShareGetInfo (16) / NetrServerGetInfo (21) ------------
static void srvsvc_dispatch(rpc_pipe_state *pipe, uint16_t opnum, uint16_t context_id,
const uint8_t *stub, uint32_t stub_len)
{
rpc_buf *out = &pipe->reply;
buf_reset(out);
if (opnum != 15 && opnum != 16 && opnum != 21)
{
put_rpc_header(out, RPC_PTYPE_FAULT, pipe->call_id);
buf_u32(out, 0); buf_u16(out, context_id); buf_u8(out, 0); buf_u8(out, 0);
buf_u32(out, 0x1c010002); // nca_s_op_rng_error
fixup_frag_length(out);
pipe->reply_pending = true;
return;
}
put_rpc_header(out, RPC_PTYPE_RESPONSE, pipe->call_id);
buf_u32(out, 0); // alloc_hint placeholder
buf_u16(out, context_id);
buf_u8(out, 0); buf_u8(out, 0);
uint32_t stub_start = out->len;
if (opnum == 15)
{
// NetrShareEnum -> [in,out] LPSHARE_ENUM_STRUCT InfoStruct, level 1,
// one entry. Explorer cross-checks the share it navigated to actually
// appears in this list before it will browse it - this used to say
// "test" (a leftover from Phase 1's own fake test share, never updated
// once tree_connect_handler started accepting real share names) while
// the real share is named "share" (see smbvfs's own share_name[] and
// what clients actually tree_connect to, \\<esp32-ip>\share) - the
// mismatch was confirmed on real hardware to make Explorer repeatedly
// re-open and re-query srvsvc many times (never trusting the mismatch
// enough to actually browse the share) before eventually giving up
// entirely, while a real TREE_CONNECT to "share" itself kept
// succeeding cleanly the whole time.
// InfoStruct is a top-level [in,out] ref pointer parameter - no
// referent ID at this level (see wkssvc_dispatch's comment above);
// Level is the first real stub byte. Everything below this - the
// union's embedded Ctr1 pointer, the Buffer array pointer, the
// per-entry name/remark pointers - are genuinely embedded/unique
// pointers and correctly keep their referent IDs.
buf_u32(out, 1); // SHARE_ENUM_STRUCT.Level
buf_u32(out, 1); // encapsulated union switch (repeats Level)
buf_u32(out, 0x00040004); // referent: SHARE_INFO_1_CONTAINER*
buf_u32(out, 1); // container.EntriesRead
buf_u32(out, 0x00040008); // referent: Buffer (array of SHARE_INFO_1)
buf_u32(out, 1); // conformant array max_count
buf_u32(out, 0x0004000c); // entry[0] referent: shi1_netname
buf_u32(out, 0); // entry[0] shi1_type: STYPE_DISKTREE
buf_u32(out, 0x00040010); // entry[0] referent: shi1_remark
buf_ndr_string(out, "share");
buf_ndr_string(out, "ZX Evolution share");
buf_u32(out, 1); // [out] TotalEntries
buf_u32(out, 0); // [in,out, unique] ResumeHandle: NULL (done)
}
else if (opnum == 16)
{
// NetrShareGetInfo -> LPSHARE_INFO union, level 1 (SHARE_INFO_1).
// Top-level [out, switch_is(Level)] ref pointer - no referent ID here
// either (see wkssvc_dispatch's comment); the union's switch value is
// the first real stub byte.
buf_u32(out, 1); // switch: level 1
buf_u32(out, 0x00030004); // referent: shi1_netname
buf_u32(out, 0); // shi1_type: STYPE_DISKTREE
buf_u32(out, 0x00030008); // referent: shi1_remark
buf_ndr_string(out, "share");
buf_ndr_string(out, "ZX Evolution share");
}
else
{
// NetrServerGetInfo -> LPSERVER_INFO union, level 100 (SERVER_INFO_100).
// Same top-level-ref-pointer-has-no-referent rule as above.
buf_u32(out, 100); // switch: level 100
buf_u32(out, 500); // sv100_platform_id = PLATFORM_ID_NT
buf_u32(out, 0x00030014); // referent: sv100_name
buf_ndr_string(out, "ZXEVO");
}
buf_u32(out, 0); // NET_API_STATUS return: NERR_Success
uint32_t stub_len_out = out->len - stub_start;
memcpy(out->data + 16, &stub_len_out, 4);
fixup_frag_length(out);
pipe->reply_pending = true;
(void)stub; (void)stub_len;
}
// ---- entry points -------------------------------------------------------
int rpc_pipe_write(const smb2_file_id file_id, const uint8_t *data, uint32_t len)
{
rpc_pipe_state *pipe = find_pipe(file_id);
if (!pipe) return -1;
if (!data) return -1;
if (len < 16)
{
ESP_LOGW(TAG, "rpc_pipe_write: PDU too short (%u)", (unsigned)len);
return -1;
}
uint8_t ptype = data[2];
uint32_t call_id; memcpy(&call_id, data + 12, 4);
pipe->call_id = call_id;
const uint8_t *body = data + 16;
uint32_t body_len = len - 16;
if (ptype == RPC_PTYPE_BIND)
{
handle_bind(pipe, body, body_len);
}
else if (ptype == RPC_PTYPE_REQUEST)
{
if (body_len < 8)
{
ESP_LOGW(TAG, "rpc request: body too short");
return -1;
}
uint16_t context_id; memcpy(&context_id, body + 4, 2);
uint16_t opnum; memcpy(&opnum, body + 6, 2);
const uint8_t *stub = body + 8;
uint32_t stub_len = body_len - 8;
if (pipe->type == RPC_PIPE_WKSSVC)
wkssvc_dispatch(pipe, opnum, context_id, stub, stub_len);
else if (pipe->type == RPC_PIPE_SRVSVC)
srvsvc_dispatch(pipe, opnum, context_id, stub, stub_len);
else
ESP_LOGW(TAG, "rpc request: pipe type=%d not wkssvc/srvsvc, opnum=%u ignored", (int)pipe->type, (unsigned)opnum);
ESP_LOGI(TAG, "rpc request: type=%d opnum=%u reply_pending=%d reply_len=%u",
(int)pipe->type, (unsigned)opnum, (int)pipe->reply_pending, (unsigned)pipe->reply.len);
}
else
{
ESP_LOGW(TAG, "rpc_pipe_write: unhandled ptype=%u", (unsigned)ptype);
return -1;
}
return 0;
}
int rpc_pipe_read(const smb2_file_id file_id, const uint8_t **out_data, uint32_t *out_len)
{
rpc_pipe_state *pipe = find_pipe(file_id);
if (!pipe || !pipe->reply_pending)
{
if (out_len) *out_len = 0;
return 0;
}
*out_data = pipe->reply.data;
*out_len = pipe->reply.len;
pipe->reply_pending = false;
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
#pragma once
// Minimal DCE/RPC-over-named-pipe support for the wkssvc/srvsvc endpoints
// Windows Explorer probes before it will browse an SMB share (see the SMB
// server plan). Only enough of MS-RPCE/MS-WKST/MS-SRVS is implemented to
// satisfy that initial probe (bind + one or two read-only info calls) -
// this is not a general-purpose RPC server.
#include <stdint.h>
#include <time.h> // smb2.h uses time_t without including <ctime> itself
#include "smb2.h"
enum rpc_pipe_type
{
RPC_PIPE_NONE = 0,
RPC_PIPE_WKSSVC,
RPC_PIPE_SRVSVC,
};
rpc_pipe_type rpc_pipe_type_for_name(const char *name);
// file_id is the SMB2 file id (16 bytes) the CREATE handler assigned - used
// to correlate the WRITE (request) and READ (response) that make up one
// RPC round trip, and to remember which pipe (and thus which RPC
// interface) a given open handle is bound to.
// owner identifies which connection this pipe belongs to (see
// rpc_pipe_reset_for_owner() below) - libsmb2 hands every handler its
// struct smb2_context* already, just thread it through.
void rpc_pipe_open(const smb2_file_id file_id, rpc_pipe_type type, struct smb2_context *owner);
void rpc_pipe_close(const smb2_file_id file_id);
// Frees every pipe slot belonging to `owner` - for use when THAT specific
// client connection is torn down (a dropped/reset TCP connection never
// sends a matching CLOSE for whatever pipes were open). Scoped to one
// owner, not a blanket reset: this table is global and can hold slots for
// more than one concurrent connection (e.g. Explorer's own IPC$ probe
// connections briefly overlapping the one it actually browses with) -
// wiping every connection's pipes just because one of them disconnected
// was confirmed on real hardware to force the *other*, still-active
// connection to restart its RPC bind from scratch in an infinite loop.
void rpc_pipe_reset_for_owner(struct smb2_context *owner);
// True if file_id is a currently-open RPC pipe handle. QUERY_INFO on a
// named pipe must report zero size - Windows' redirector uses a nonzero
// end_of_file as a sign this isn't really a pipe and quietly gives up
// without ever attempting the RPC bind (confirmed by packet capture: it
// was doing CREATE -> QUERY_INFO -> CLOSE in a loop, never WRITE).
bool rpc_pipe_is_open(const smb2_file_id file_id);
// Feeds a WRITE's payload (a DCE/RPC PDU: bind or request) to the pipe
// identified by file_id. Builds the reply PDU internally; returns 0 on
// success (including "we didn't understand this, queued a fault reply"),
// <0 if file_id isn't a known open RPC pipe.
int rpc_pipe_write(const smb2_file_id file_id, const uint8_t *data, uint32_t len);
// Retrieves the reply PDU queued by the preceding write, if any. *out_data
// points into internal storage valid until the next write/close on this
// file_id. Returns 0 with out_len=0 if nothing is queued.
int rpc_pipe_read(const smb2_file_id file_id, const uint8_t **out_data, uint32_t *out_len);
+149
View File
@@ -0,0 +1,149 @@
#include <string.h>
#include <stdlib.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "main.h"
#include "nvs_params.h"
app_params_t app_params;
char NVS_PARAMS_NS[] = "appcfg";
char NVS_KEY_USB_MODE[] = "usb_mode";
char NVS_KEY_WIFI_MODE[] = "wifi_mode";
char NVS_KEY_WIFI_AP[] = "wifi_ap";
char NVS_KEY_WIFI_PSW[] = "wifi_psw";
void initialize_nvs()
{
esp_err_t err = nvs_flash_init();
log_sram_used(__FILE_NAME__ ": nvs_flash_init");
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
log_sram_used(__FILE_NAME__ ": initialize_nvs end");
}
void app_params_set_defaults()
{
memset(&app_params, 0, sizeof(app_params));
}
esp_err_t app_params_load_u8(nvs_handle_t h, const char *key, uint8_t *value)
{
esp_err_t err = nvs_get_u8(h, key, value);
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
return err;
}
esp_err_t app_params_load_str(nvs_handle_t h, const char *key, char *buf, size_t buf_size)
{
size_t size = buf_size;
esp_err_t err = nvs_get_str(h, key, buf, &size);
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
if (err == ESP_ERR_NVS_INVALID_LENGTH)
{
buf[0] = 0;
return ESP_OK;
}
return err;
}
esp_err_t app_params_load()
{
app_params_set_defaults();
nvs_handle_t h = 0;
esp_err_t err = nvs_open(NVS_PARAMS_NS, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
if (err != ESP_OK) return err;
err = app_params_load_u8(h, NVS_KEY_USB_MODE, &app_params.usb_mode);
if (err != ESP_OK) goto end;
err = app_params_load_u8(h, NVS_KEY_WIFI_MODE, &app_params.wifi_mode);
if (err != ESP_OK) goto end;
err = app_params_load_str(h, NVS_KEY_WIFI_AP, app_params.wifi_ap, sizeof(app_params.wifi_ap));
if (err != ESP_OK) goto end;
err = app_params_load_str(h, NVS_KEY_WIFI_PSW, app_params.wifi_psw, sizeof(app_params.wifi_psw));
if (err != ESP_OK) goto end;
end:
nvs_close(h);
return err;
}
esp_err_t app_params_save()
{
nvs_handle_t h = 0;
esp_err_t err = nvs_open(NVS_PARAMS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = nvs_set_u8(h, NVS_KEY_USB_MODE, app_params.usb_mode);
if (err != ESP_OK) goto end;
err = nvs_set_u8(h, NVS_KEY_WIFI_MODE, app_params.wifi_mode);
if (err != ESP_OK) goto end;
err = nvs_set_str(h, NVS_KEY_WIFI_AP, app_params.wifi_ap);
if (err != ESP_OK) goto end;
err = nvs_set_str(h, NVS_KEY_WIFI_PSW, app_params.wifi_psw);
if (err != ESP_OK) goto end;
err = nvs_commit(h);
end:
nvs_close(h);
return err;
}
esp_err_t app_params_reset()
{
app_params_set_defaults();
return app_params_save();
}
bool app_params_set_by_name(const char *name, const char *value)
{
char *endp = NULL;
unsigned long v = 0;
if (!strcmp(name, "usb_mode"))
{
v = strtoul(value, &endp, 0);
if (!endp || *endp || v > 255) return false;
app_params.usb_mode = (uint8_t)v;
return true;
}
if (!strcmp(name, "wifi_mode"))
{
v = strtoul(value, &endp, 0);
if (!endp || *endp || v > 255) return false;
app_params.wifi_mode = (uint8_t)v;
return true;
}
if (!strcmp(name, "wifi_ap"))
{
strlcpy(app_params.wifi_ap, value, sizeof(app_params.wifi_ap));
return true;
}
if (!strcmp(name, "wifi_psw"))
{
strlcpy(app_params.wifi_psw, value, sizeof(app_params.wifi_psw));
return true;
}
return false;
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
struct app_params_t
{
uint8_t usb_mode;
uint8_t wifi_mode;
char wifi_ap[64];
char wifi_psw[64];
};
extern app_params_t app_params;
void initialize_nvs();
void app_params_set_defaults();
esp_err_t app_params_load();
esp_err_t app_params_save();
esp_err_t app_params_reset();
bool app_params_set_by_name(const char *name, const char *value);
+4620
View File
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include "esp_err.h"
#include "main.h"
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum
{
OPL_MODE_OFF = 0,
OPL_MODE_YMF262,
OPL_MODE_YMF278
} OPL_MODE;
typedef enum
{
OPL_FM_RENDER_OPL3_FULL = 0,
OPL_FM_RENDER_OPL2_MELODY
} OPL_FM_RENDER_MODE;
#define OPL_PORT_FM_REG1 0x00C4
#define OPL_PORT_FM_DAT1 0x00C5
#define OPL_PORT_FM_REG2 0x00C6
#define OPL_PORT_FM_DAT2 0x00C7
#define OPL_PORT_WAVE_REG 0x007E
#define OPL_PORT_WAVE_DAT 0x007F
#define OPL_ROM_PART_LABEL "oplrom"
#define OPL_ROM_PART_SUBTYPE 0x85
#define OPL_ROM_SIZE 0x200000
#define OPL_YMF278_DEFAULT_RAM_SIZE 0x400000
#define OPL_FM_BANK_COUNT 2
#define OPL_FM_REG_COUNT 256
#define OPL_WAVE_REG_COUNT 256
typedef struct
{
u16 reg;
u8 value;
} OPL_FM_REG_WRITE;
typedef struct
{
OPL_MODE mode;
u8 enabled;
size_t rom_size;
size_t ram_size;
u8 fm_latch_bank;
u8 fm_latch[2];
u8 wave_latch;
u8 status;
u32 queued_writes;
} OPL_INFO;
esp_err_t opl_enable(OPL_MODE mode, size_t ram_size);
void opl_disable();
void opl_reset();
int opl_is_enabled();
esp_err_t opl_get_info(OPL_INFO *info);
u8 opl_read_status();
u8 opl_read_port(u16 port);
void opl_write_port(u16 port, u8 value);
esp_err_t opl_queue_write_port(u16 port, u8 value);
esp_err_t opl_queue_write_fm_reg(u16 reg, u8 value);
esp_err_t opl_queue_write_wave_reg(u8 reg, u8 value);
u8 opl_read_fm_reg(u16 reg);
void opl_write_fm_reg(u16 reg, u8 value);
esp_err_t opl_write_fm_regs(const OPL_FM_REG_WRITE *writes, size_t count);
u8 opl_read_wave_reg(u8 reg);
void opl_write_wave_reg(u8 reg, u8 value);
esp_err_t opl_set_fm_render_mode(OPL_FM_RENDER_MODE mode);
esp_err_t opl_set_volume(u8 volume);
u8 opl_get_volume();
void opl_render(float *mix, int sample_count, int sample_rate);
void opl_console_register_system_commands();
#ifdef __cplusplus
}
#endif
+922
View File
@@ -0,0 +1,922 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_console.h"
#include "esp_err.h"
#include "esp_intr_alloc.h"
#include "ps2_mouse.h"
const gpio_num_t PS2_MOUSE_GPIO_DATA = GPIO_NUM_5;
const gpio_num_t PS2_MOUSE_GPIO_CLK = GPIO_NUM_7;
enum
{
PS2_TX_FIFO_SIZE = 64,
PS2_CLK_LOW_US = 35,
PS2_CLK_HIGH_US = 35,
PS2_TX_HIGH_HOLD_US = 15,
PS2_TX_SETUP_US = 20,
PS2_TX_INTERBYTE_US = 80,
PS2_HOST_RX_START_US = 30,
PS2_HOST_RX_SAMPLE_US = 18,
PS2_HOST_ACK_SETUP_US = 15,
PS2_HOST_INHIBIT_US = 120,
PS2_TX_RETRY_US = 200,
};
enum ps2_bus_mode_t
{
PS2_BUS_IDLE = 0,
PS2_BUS_DEV_TX,
PS2_BUS_HOST_RX,
PS2_BUS_HOST_ACK,
};
enum ps2_timer_evt_t
{
PS2_TIMER_EVT_NONE = 0,
PS2_TIMER_EVT_DEV_TX_FALL,
PS2_TIMER_EVT_DEV_TX_RISE,
PS2_TIMER_EVT_DEV_TX_NEXT,
PS2_TIMER_EVT_DEV_TX_DONE,
PS2_TIMER_EVT_HOST_RX_FALL,
PS2_TIMER_EVT_HOST_RX_RISE,
PS2_TIMER_EVT_HOST_RX_SAMPLE,
PS2_TIMER_EVT_HOST_ACK_PREPARE,
PS2_TIMER_EVT_HOST_ACK_FALL,
PS2_TIMER_EVT_HOST_ACK_RISE,
PS2_TIMER_EVT_HOST_ACK_DONE,
PS2_TIMER_EVT_TX_RETRY,
};
gptimer_handle_t ps2_mouse_timer = NULL;
portMUX_TYPE ps2_mouse_lock = portMUX_INITIALIZER_UNLOCKED;
bool ps2_mouse_gpio_isr_service_ready = false;
volatile bool ps2_mouse_active = false;
volatile bool ps2_mouse_streaming = false;
volatile bool ps2_mouse_remote_mode = false;
volatile bool ps2_mouse_scaling21 = false;
volatile bool ps2_mouse_expect_param = false;
volatile uint8_t ps2_mouse_pending_cmd = 0;
volatile uint8_t ps2_mouse_sample_rate = 100;
volatile uint8_t ps2_mouse_resolution = 2;
volatile int ps2_mouse_dx_accum = 0;
volatile int ps2_mouse_dy_accum = 0;
volatile uint8_t ps2_mouse_buttons = 0;
volatile bool ps2_mouse_motion_pending = false;
volatile uint8_t ps2_mouse_tx_fifo[PS2_TX_FIFO_SIZE];
volatile unsigned ps2_mouse_tx_rd = 0;
volatile unsigned ps2_mouse_tx_wr = 0;
volatile unsigned ps2_mouse_tx_count = 0;
volatile bool ps2_mouse_tx_resume_valid = false;
volatile uint8_t ps2_mouse_tx_resume_byte = 0;
volatile ps2_bus_mode_t ps2_mouse_bus_mode = PS2_BUS_IDLE;
volatile ps2_timer_evt_t ps2_mouse_timer_evt = PS2_TIMER_EVT_NONE;
volatile uint8_t ps2_mouse_tx_byte = 0;
volatile uint8_t ps2_mouse_tx_parity = 1;
volatile int ps2_mouse_tx_bit = 0;
volatile uint8_t ps2_mouse_rx_byte = 0;
volatile uint8_t ps2_mouse_rx_parity = 1;
volatile int ps2_mouse_rx_bit = 0;
volatile bool ps2_mouse_rx_start_ok = false;
volatile bool ps2_mouse_rx_parity_ok = false;
volatile bool ps2_mouse_rx_stop_ok = false;
volatile uint8_t ps2_mouse_rx_done_byte = 0;
volatile bool ps2_mouse_rx_frame_ok = false;
volatile uint32_t ps2_mouse_clk_low_since_us = 0;
void IRAM_ATTR ps2_mouse_clk_low()
{
gpio_set_level(PS2_MOUSE_GPIO_CLK, 0);
}
void IRAM_ATTR ps2_mouse_clk_release()
{
gpio_set_level(PS2_MOUSE_GPIO_CLK, 1);
}
void IRAM_ATTR ps2_mouse_data_low()
{
gpio_set_level(PS2_MOUSE_GPIO_DATA, 0);
}
void IRAM_ATTR ps2_mouse_data_release()
{
gpio_set_level(PS2_MOUSE_GPIO_DATA, 1);
}
int IRAM_ATTR ps2_mouse_clk_level()
{
return gpio_get_level(PS2_MOUSE_GPIO_CLK);
}
int IRAM_ATTR ps2_mouse_data_level()
{
return gpio_get_level(PS2_MOUSE_GPIO_DATA);
}
int IRAM_ATTR ps2_mouse_line_idle()
{
return ps2_mouse_clk_level() && ps2_mouse_data_level();
}
uint32_t IRAM_ATTR ps2_mouse_get_time_us_isr()
{
uint64_t now = 0;
if (!ps2_mouse_timer)
return 0;
gptimer_get_raw_count(ps2_mouse_timer, &now);
return (uint32_t)now;
}
void IRAM_ATTR ps2_mouse_timer_schedule_isr(ps2_timer_evt_t evt, uint32_t delta_us)
{
uint64_t now = 0;
gptimer_alarm_config_t alarm = {};
if (!ps2_mouse_timer)
return;
gptimer_get_raw_count(ps2_mouse_timer, &now);
ps2_mouse_timer_evt = evt;
alarm.alarm_count = now + delta_us;
alarm.flags.auto_reload_on_alarm = false;
gptimer_set_alarm_action(ps2_mouse_timer, &alarm);
}
void IRAM_ATTR ps2_mouse_reset_device_state_isr()
{
ps2_mouse_streaming = false;
ps2_mouse_remote_mode = false;
ps2_mouse_scaling21 = false;
ps2_mouse_expect_param = false;
ps2_mouse_pending_cmd = 0;
ps2_mouse_sample_rate = 100;
ps2_mouse_resolution = 2;
ps2_mouse_dx_accum = 0;
ps2_mouse_dy_accum = 0;
ps2_mouse_buttons = 0;
ps2_mouse_motion_pending = false;
}
void IRAM_ATTR ps2_mouse_reset_protocol_state_isr()
{
ps2_mouse_reset_device_state_isr();
ps2_mouse_tx_rd = 0;
ps2_mouse_tx_wr = 0;
ps2_mouse_tx_count = 0;
ps2_mouse_tx_resume_valid = false;
ps2_mouse_tx_resume_byte = 0;
ps2_mouse_bus_mode = PS2_BUS_IDLE;
ps2_mouse_timer_evt = PS2_TIMER_EVT_NONE;
ps2_mouse_tx_byte = 0;
ps2_mouse_tx_parity = 1;
ps2_mouse_tx_bit = 0;
ps2_mouse_rx_byte = 0;
ps2_mouse_rx_parity = 1;
ps2_mouse_rx_bit = 0;
ps2_mouse_rx_start_ok = false;
ps2_mouse_rx_parity_ok = false;
ps2_mouse_rx_stop_ok = false;
ps2_mouse_rx_done_byte = 0;
ps2_mouse_rx_frame_ok = false;
ps2_mouse_clk_low_since_us = 0;
}
int IRAM_ATTR ps2_mouse_tx_push_isr(uint8_t v)
{
if (ps2_mouse_tx_count >= PS2_TX_FIFO_SIZE)
return 0;
ps2_mouse_tx_fifo[ps2_mouse_tx_wr] = v;
ps2_mouse_tx_wr = ps2_mouse_tx_wr + 1;
if (ps2_mouse_tx_wr >= PS2_TX_FIFO_SIZE)
ps2_mouse_tx_wr = 0;
ps2_mouse_tx_count = ps2_mouse_tx_count + 1;
return 1;
}
int IRAM_ATTR ps2_mouse_tx_pop_isr(uint8_t *v)
{
if (ps2_mouse_tx_resume_valid)
{
*v = ps2_mouse_tx_resume_byte;
ps2_mouse_tx_resume_valid = false;
return 1;
}
if (!ps2_mouse_tx_count)
return 0;
*v = ps2_mouse_tx_fifo[ps2_mouse_tx_rd];
ps2_mouse_tx_rd = ps2_mouse_tx_rd + 1;
if (ps2_mouse_tx_rd >= PS2_TX_FIFO_SIZE)
ps2_mouse_tx_rd = 0;
ps2_mouse_tx_count = ps2_mouse_tx_count - 1;
return 1;
}
int IRAM_ATTR ps2_mouse_clamp_9bit(int v)
{
if (v > 255)
return 255;
if (v < -255)
return -255;
return v;
}
void IRAM_ATTR ps2_mouse_build_packet_isr(uint8_t packet[3], bool clear_motion)
{
int dx = ps2_mouse_clamp_9bit(ps2_mouse_dx_accum);
int dy = ps2_mouse_clamp_9bit(ps2_mouse_dy_accum);
packet[0] = 0x08 | (ps2_mouse_buttons & 0x07);
if (dx < 0)
packet[0] |= 0x10;
if (dy < 0)
packet[0] |= 0x20;
if (dx != ps2_mouse_dx_accum)
packet[0] |= 0x40;
if (dy != ps2_mouse_dy_accum)
packet[0] |= 0x80;
packet[1] = (uint8_t)(dx & 0xFF);
packet[2] = (uint8_t)(dy & 0xFF);
if (clear_motion)
{
ps2_mouse_dx_accum = 0;
ps2_mouse_dy_accum = 0;
ps2_mouse_motion_pending = false;
}
}
void IRAM_ATTR ps2_mouse_queue_motion_if_possible_isr()
{
uint8_t packet[3];
if (!ps2_mouse_streaming)
return;
if (ps2_mouse_remote_mode)
return;
if (!ps2_mouse_motion_pending)
return;
if (ps2_mouse_tx_count > (PS2_TX_FIFO_SIZE - 3))
return;
ps2_mouse_build_packet_isr(packet, true);
ps2_mouse_tx_push_isr(packet[0]);
ps2_mouse_tx_push_isr(packet[1]);
ps2_mouse_tx_push_isr(packet[2]);
}
int IRAM_ATTR ps2_mouse_get_tx_bit_isr(int bit_index)
{
if (bit_index == 0)
return 0;
if (bit_index >= 1 && bit_index <= 8)
return (ps2_mouse_tx_byte >> (bit_index - 1)) & 1;
if (bit_index == 9)
return ps2_mouse_tx_parity;
return 1;
}
void IRAM_ATTR ps2_mouse_drive_tx_bit_isr(int bit_index)
{
if (ps2_mouse_get_tx_bit_isr(bit_index))
ps2_mouse_data_release();
else
ps2_mouse_data_low();
}
void IRAM_ATTR ps2_mouse_prepare_tx_byte_isr(uint8_t v)
{
ps2_mouse_tx_byte = v;
ps2_mouse_tx_parity = 1;
for (int i = 0; i < 8; i++)
ps2_mouse_tx_parity ^= (uint8_t)((v >> i) & 1);
ps2_mouse_tx_bit = 0;
ps2_mouse_bus_mode = PS2_BUS_DEV_TX;
ps2_mouse_clk_release();
ps2_mouse_drive_tx_bit_isr(0);
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_FALL, PS2_TX_SETUP_US);
}
int IRAM_ATTR ps2_mouse_host_inhibit_active_isr()
{
uint32_t now;
if (ps2_mouse_clk_level())
return 0;
now = ps2_mouse_get_time_us_isr();
if ((uint32_t)(now - ps2_mouse_clk_low_since_us) < PS2_HOST_INHIBIT_US)
return 0;
return 1;
}
void IRAM_ATTR ps2_mouse_dev_tx_abort_isr()
{
if (ps2_mouse_bus_mode != PS2_BUS_DEV_TX)
return;
ps2_mouse_tx_resume_valid = true;
ps2_mouse_tx_resume_byte = ps2_mouse_tx_byte;
ps2_mouse_data_release();
ps2_mouse_clk_release();
ps2_mouse_bus_mode = PS2_BUS_IDLE;
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_RETRY_US);
}
void IRAM_ATTR ps2_mouse_try_start_tx_isr()
{
uint8_t v = 0;
if (!ps2_mouse_active)
return;
if (ps2_mouse_bus_mode != PS2_BUS_IDLE)
return;
ps2_mouse_queue_motion_if_possible_isr();
if (!ps2_mouse_tx_resume_valid && !ps2_mouse_tx_count)
return;
if (!ps2_mouse_line_idle())
{
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_RETRY_US);
return;
}
if (!ps2_mouse_tx_pop_isr(&v))
return;
ps2_mouse_prepare_tx_byte_isr(v);
}
void IRAM_ATTR ps2_mouse_start_host_rx_isr()
{
if (!ps2_mouse_active)
return;
if (ps2_mouse_bus_mode != PS2_BUS_IDLE)
return;
ps2_mouse_bus_mode = PS2_BUS_HOST_RX;
ps2_mouse_rx_byte = 0;
ps2_mouse_rx_parity = 1;
ps2_mouse_rx_bit = 0;
ps2_mouse_rx_start_ok = true;
ps2_mouse_rx_parity_ok = false;
ps2_mouse_rx_stop_ok = false;
ps2_mouse_rx_done_byte = 0;
ps2_mouse_rx_frame_ok = false;
ps2_mouse_data_release();
ps2_mouse_clk_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_FALL, PS2_HOST_RX_START_US);
}
void IRAM_ATTR ps2_mouse_host_rx_sample_isr()
{
int dat = ps2_mouse_data_level();
if (ps2_mouse_rx_bit >= 0 && ps2_mouse_rx_bit <= 7)
{
if (dat)
ps2_mouse_rx_byte |= (uint8_t)(1U << ps2_mouse_rx_bit);
ps2_mouse_rx_parity ^= (uint8_t)dat;
}
else if (ps2_mouse_rx_bit == 8)
{
ps2_mouse_rx_parity_ok = ((uint8_t)dat == ps2_mouse_rx_parity);
}
else if (ps2_mouse_rx_bit == 9)
{
ps2_mouse_rx_stop_ok = (dat == 1);
ps2_mouse_rx_done_byte = ps2_mouse_rx_byte;
ps2_mouse_rx_frame_ok = ps2_mouse_rx_start_ok && ps2_mouse_rx_parity_ok && ps2_mouse_rx_stop_ok;
}
ps2_mouse_rx_bit = ps2_mouse_rx_bit + 1;
}
void IRAM_ATTR ps2_mouse_handle_cmd_isr(uint8_t value)
{
uint8_t packet[3];
uint8_t status = 0;
if (ps2_mouse_expect_param)
{
ps2_mouse_expect_param = false;
if (ps2_mouse_pending_cmd == 0xF3)
ps2_mouse_sample_rate = value;
if (ps2_mouse_pending_cmd == 0xE8)
ps2_mouse_resolution = value & 0x03;
ps2_mouse_pending_cmd = 0;
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_try_start_tx_isr();
return;
}
switch (value)
{
case 0xFF:
ps2_mouse_reset_device_state_isr();
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_tx_push_isr(0xAA);
ps2_mouse_tx_push_isr(0x00);
break;
case 0xF6:
ps2_mouse_reset_device_state_isr();
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xF5:
ps2_mouse_streaming = false;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xF4:
ps2_mouse_streaming = true;
ps2_mouse_remote_mode = false;
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_queue_motion_if_possible_isr();
break;
case 0xF2:
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_tx_push_isr(0x00);
break;
case 0xEB:
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_build_packet_isr(packet, true);
ps2_mouse_tx_push_isr(packet[0]);
ps2_mouse_tx_push_isr(packet[1]);
ps2_mouse_tx_push_isr(packet[2]);
break;
case 0xEA:
ps2_mouse_remote_mode = false;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xF0:
ps2_mouse_remote_mode = true;
ps2_mouse_streaming = false;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xE6:
ps2_mouse_scaling21 = false;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xE7:
ps2_mouse_scaling21 = true;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xE9:
if (ps2_mouse_scaling21)
status |= 0x10;
if (ps2_mouse_streaming)
status |= 0x20;
if (ps2_mouse_remote_mode)
status |= 0x40;
status |= (ps2_mouse_buttons & 0x07);
ps2_mouse_tx_push_isr(0xFA);
ps2_mouse_tx_push_isr(status);
ps2_mouse_tx_push_isr(ps2_mouse_resolution);
ps2_mouse_tx_push_isr(ps2_mouse_sample_rate);
break;
case 0xF3:
ps2_mouse_expect_param = true;
ps2_mouse_pending_cmd = 0xF3;
ps2_mouse_tx_push_isr(0xFA);
break;
case 0xE8:
ps2_mouse_expect_param = true;
ps2_mouse_pending_cmd = 0xE8;
ps2_mouse_tx_push_isr(0xFA);
break;
default:
ps2_mouse_tx_push_isr(0xFE);
break;
}
ps2_mouse_try_start_tx_isr();
}
bool IRAM_ATTR ps2_mouse_timer_cb(gptimer_handle_t, const gptimer_alarm_event_data_t *, void *)
{
portENTER_CRITICAL_ISR(&ps2_mouse_lock);
if (!ps2_mouse_active)
{
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
return false;
}
switch (ps2_mouse_timer_evt)
{
case PS2_TIMER_EVT_DEV_TX_FALL:
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
{
if (ps2_mouse_host_inhibit_active_isr())
{
ps2_mouse_dev_tx_abort_isr();
break;
}
ps2_mouse_clk_low();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_RISE, PS2_CLK_LOW_US);
}
break;
case PS2_TIMER_EVT_DEV_TX_RISE:
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
{
ps2_mouse_clk_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_NEXT, PS2_TX_HIGH_HOLD_US);
}
break;
case PS2_TIMER_EVT_DEV_TX_NEXT:
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
{
if (ps2_mouse_host_inhibit_active_isr())
{
ps2_mouse_dev_tx_abort_isr();
break;
}
ps2_mouse_tx_bit = ps2_mouse_tx_bit + 1;
if (ps2_mouse_tx_bit <= 10)
{
ps2_mouse_drive_tx_bit_isr(ps2_mouse_tx_bit);
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_FALL, PS2_TX_SETUP_US);
}
else
{
ps2_mouse_data_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_DONE, PS2_TX_SETUP_US);
}
}
break;
case PS2_TIMER_EVT_DEV_TX_DONE:
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
{
ps2_mouse_data_release();
ps2_mouse_clk_release();
ps2_mouse_bus_mode = PS2_BUS_IDLE;
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_INTERBYTE_US);
}
break;
case PS2_TIMER_EVT_HOST_RX_FALL:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
{
ps2_mouse_data_release();
ps2_mouse_clk_low();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_RISE, PS2_CLK_LOW_US);
}
break;
case PS2_TIMER_EVT_HOST_RX_RISE:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
{
ps2_mouse_clk_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_SAMPLE, PS2_HOST_RX_SAMPLE_US);
}
break;
case PS2_TIMER_EVT_HOST_RX_SAMPLE:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
{
ps2_mouse_host_rx_sample_isr();
if (ps2_mouse_rx_bit <= 9)
{
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_FALL, PS2_CLK_HIGH_US - PS2_HOST_RX_SAMPLE_US);
}
else
{
ps2_mouse_bus_mode = PS2_BUS_HOST_ACK;
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_PREPARE, PS2_CLK_HIGH_US - PS2_HOST_RX_SAMPLE_US);
}
}
break;
case PS2_TIMER_EVT_HOST_ACK_PREPARE:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
{
if (ps2_mouse_rx_frame_ok)
ps2_mouse_data_low();
else
ps2_mouse_data_release();
ps2_mouse_clk_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_FALL, PS2_HOST_ACK_SETUP_US);
}
break;
case PS2_TIMER_EVT_HOST_ACK_FALL:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
{
ps2_mouse_clk_low();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_RISE, PS2_CLK_LOW_US);
}
break;
case PS2_TIMER_EVT_HOST_ACK_RISE:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
{
ps2_mouse_clk_release();
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_DONE, PS2_CLK_HIGH_US);
}
break;
case PS2_TIMER_EVT_HOST_ACK_DONE:
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
{
ps2_mouse_data_release();
ps2_mouse_clk_release();
ps2_mouse_bus_mode = PS2_BUS_IDLE;
if (ps2_mouse_rx_frame_ok)
ps2_mouse_handle_cmd_isr(ps2_mouse_rx_done_byte);
else
ps2_mouse_try_start_tx_isr();
}
break;
case PS2_TIMER_EVT_TX_RETRY:
if (ps2_mouse_bus_mode == PS2_BUS_IDLE)
ps2_mouse_try_start_tx_isr();
break;
default:
break;
}
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
return false;
}
void IRAM_ATTR ps2_mouse_clk_isr(void *)
{
int clk;
int dat;
uint32_t now;
uint32_t low_time;
portENTER_CRITICAL_ISR(&ps2_mouse_lock);
if (!ps2_mouse_active)
{
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
return;
}
clk = ps2_mouse_clk_level();
dat = ps2_mouse_data_level();
now = ps2_mouse_get_time_us_isr();
if (!clk)
{
ps2_mouse_clk_low_since_us = now;
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
return;
}
low_time = now - ps2_mouse_clk_low_since_us;
if (ps2_mouse_bus_mode == PS2_BUS_IDLE)
{
if (low_time >= PS2_HOST_INHIBIT_US && !dat)
{
ps2_mouse_start_host_rx_isr();
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
return;
}
}
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
}
void IRAM_ATTR ps2_mouse_data_isr(void *)
{
}
esp_err_t ps2_mouse_init()
{
esp_err_t err;
gpio_config_t io = {};
gptimer_config_t tcfg = {};
gptimer_event_callbacks_t tcbs = {};
io.pin_bit_mask = (1ULL << PS2_MOUSE_GPIO_DATA) | (1ULL << PS2_MOUSE_GPIO_CLK);
io.mode = GPIO_MODE_INPUT_OUTPUT_OD;
io.pull_up_en = GPIO_PULLUP_ENABLE;
io.pull_down_en = GPIO_PULLDOWN_DISABLE;
io.intr_type = GPIO_INTR_DISABLE;
err = gpio_config(&io);
if (err != ESP_OK) return err;
err = gpio_set_pull_mode(PS2_MOUSE_GPIO_DATA, GPIO_PULLUP_ONLY);
if (err != ESP_OK) return err;
err = gpio_set_pull_mode(PS2_MOUSE_GPIO_CLK, GPIO_PULLUP_ONLY);
if (err != ESP_OK) return err;
ps2_mouse_data_release();
ps2_mouse_clk_release();
tcfg.clk_src = GPTIMER_CLK_SRC_DEFAULT;
tcfg.direction = GPTIMER_COUNT_UP;
tcfg.resolution_hz = 1000000;
err = gptimer_new_timer(&tcfg, &ps2_mouse_timer);
if (err != ESP_OK) return err;
tcbs.on_alarm = ps2_mouse_timer_cb;
err = gptimer_register_event_callbacks(ps2_mouse_timer, &tcbs, NULL);
if (err != ESP_OK) return err;
err = gptimer_enable(ps2_mouse_timer);
if (err != ESP_OK) return err;
err = gptimer_start(ps2_mouse_timer);
if (err != ESP_OK) return err;
if (!ps2_mouse_gpio_isr_service_ready)
{
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if (err == ESP_ERR_INVALID_STATE)
ps2_mouse_gpio_isr_service_ready = true;
else if (err != ESP_OK)
return err;
else
ps2_mouse_gpio_isr_service_ready = true;
}
err = gpio_set_intr_type(PS2_MOUSE_GPIO_CLK, GPIO_INTR_ANYEDGE);
if (err != ESP_OK) return err;
err = gpio_set_intr_type(PS2_MOUSE_GPIO_DATA, GPIO_INTR_ANYEDGE);
if (err != ESP_OK) return err;
err = gpio_isr_handler_add(PS2_MOUSE_GPIO_CLK, ps2_mouse_clk_isr, NULL);
if (err != ESP_OK) return err;
err = gpio_isr_handler_add(PS2_MOUSE_GPIO_DATA, ps2_mouse_data_isr, NULL);
if (err != ESP_OK) return err;
ps2_mouse_clk_low_since_us = 0;
return ESP_OK;
}
void ps2_mouse_deinit()
{
gpio_intr_disable(PS2_MOUSE_GPIO_CLK);
gpio_intr_disable(PS2_MOUSE_GPIO_DATA);
gpio_isr_handler_remove(PS2_MOUSE_GPIO_CLK);
gpio_isr_handler_remove(PS2_MOUSE_GPIO_DATA);
ps2_mouse_data_release();
ps2_mouse_clk_release();
if (ps2_mouse_timer)
{
gptimer_stop(ps2_mouse_timer);
gptimer_disable(ps2_mouse_timer);
gptimer_del_timer(ps2_mouse_timer);
ps2_mouse_timer = NULL;
}
}
bool ps2_mouse_is_active()
{
return ps2_mouse_active;
}
esp_err_t ps2_mouse_send_movement(int dx, int dy, unsigned buttons)
{
if (!ps2_mouse_active)
return ESP_ERR_INVALID_STATE;
portENTER_CRITICAL(&ps2_mouse_lock);
if (!ps2_mouse_streaming || ps2_mouse_remote_mode)
{
portEXIT_CRITICAL(&ps2_mouse_lock);
return ESP_ERR_INVALID_STATE;
}
ps2_mouse_dx_accum += dx;
ps2_mouse_dy_accum += dy;
ps2_mouse_buttons = (uint8_t)(buttons & 0x07);
ps2_mouse_motion_pending = true;
ps2_mouse_queue_motion_if_possible_isr();
ps2_mouse_try_start_tx_isr();
portEXIT_CRITICAL(&ps2_mouse_lock);
return ESP_OK;
}
esp_err_t ps2_mouse_start()
{
esp_err_t err;
if (ps2_mouse_active)
return ESP_OK;
portENTER_CRITICAL(&ps2_mouse_lock);
ps2_mouse_active = true;
ps2_mouse_reset_protocol_state_isr();
portEXIT_CRITICAL(&ps2_mouse_lock);
err = ps2_mouse_init();
if (err != ESP_OK)
{
portENTER_CRITICAL(&ps2_mouse_lock);
ps2_mouse_active = false;
portEXIT_CRITICAL(&ps2_mouse_lock);
ps2_mouse_deinit();
return err;
}
portENTER_CRITICAL(&ps2_mouse_lock);
ps2_mouse_streaming = true;
ps2_mouse_remote_mode = false;
ps2_mouse_expect_param = false;
ps2_mouse_pending_cmd = 0;
portEXIT_CRITICAL(&ps2_mouse_lock);
ESP_LOGI("ps2_mouse", "ps/2 mouse started on GPIO%u=data, GPIO%u=clk, streaming enabled", PS2_MOUSE_GPIO_DATA, PS2_MOUSE_GPIO_CLK);
return ESP_OK;
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "esp_err.h"
esp_err_t ps2_mouse_start();
esp_err_t ps2_mouse_send_movement(int dx, int dy, unsigned buttons);
bool ps2_mouse_is_active();
+1207
View File
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_cmd.h"
#include "sdmmc.h"
#if defined(CONFIG_IDF_TARGET_ESP32P4)
#define SD_SLOT SDMMC_HOST_SLOT_1
#define SD_CLK GPIO_NUM_43
#define SD_CMD GPIO_NUM_44
#define SD_D0 GPIO_NUM_39
#define SD_D1 GPIO_NUM_40
#define SD_D2 GPIO_NUM_41
#define SD_D3 GPIO_NUM_42
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#define SD_SLOT SDMMC_HOST_SLOT_1
#define SD_CLK GPIO_NUM_40
#define SD_CMD GPIO_NUM_39
#define SD_D0 GPIO_NUM_38
#define SD_D1 GPIO_NUM_2
#define SD_D2 GPIO_NUM_41
#define SD_D3 GPIO_NUM_42
#endif
#ifndef SDMMC_CMD13_SEND_STATUS
#define SDMMC_CMD13_SEND_STATUS 13U
#endif
#ifndef SDMMC_ARG_RCA
#define SDMMC_ARG_RCA(rca_) ((uint32_t)(rca_) << 16)
#endif
#ifndef SDMMC_R1
#define SDMMC_R1(resp_) ((uint32_t)((resp_)[0]))
#endif
#ifndef SDMMC_CMD7_SELECT_CARD
#define SDMMC_CMD7_SELECT_CARD 7U
#endif
#define SD_ST_OUT_OF_RANGE (1U << 31)
#define SD_ST_ADDRESS_ERROR (1U << 30)
#define SD_ST_BLOCK_LEN_ERROR (1U << 29)
#define SD_ST_ERASE_SEQ_ERROR (1U << 28)
#define SD_ST_ERASE_PARAM (1U << 27)
#define SD_ST_WP_VIOLATION (1U << 26)
#define SD_ST_CARD_IS_LOCKED (1U << 25)
#define SD_ST_LOCK_UNLOCK_FAILED (1U << 24)
#define SD_ST_COM_CRC_ERROR (1U << 23)
#define SD_ST_ILLEGAL_COMMAND (1U << 22)
#define SD_ST_CARD_ECC_FAILED (1U << 21)
#define SD_ST_CC_ERROR (1U << 20)
#define SD_ST_ERROR (1U << 19)
#define SD_ST_CSD_OVERWRITE (1U << 16)
#define SD_ST_WP_ERASE_SKIP (1U << 15)
#define SD_ST_ERASE_RESET (1U << 13)
extern sdmmc_host_t sd_host;
extern sdmmc_slot_config_t sd_slot;
extern sdmmc_card_t sd_card;
extern sdmmc_card_t *sd_card_ptr;
extern bool sd_initialized;
extern bool sd_fs_mounted;
extern char sd_fs_base_path[32];
extern sdmmc_card_t *sd_fs_card;
esp_err_t sd_init();
esp_err_t sd_reinit();
esp_err_t sd_ensure_ready();
esp_err_t sd_probe_card();
esp_err_t sd_fs_sense(const char *base_path);
esp_log_level_t sd_host_log_suppress_begin();
void sd_host_log_suppress_end(esp_log_level_t old_sd_host_level);
void sd_fs_unmount_force();
void sd_deinit();
esp_err_t sd_read_sectors(uint32_t sec, uint32_t num);
esp_err_t sd_card_erase();
esp_err_t sd_fs_mount(const char *base_path, sdmmc_card_t **out_card);
esp_err_t sd_fs_mount_quiet(const char *base_path, sdmmc_card_t **out_card);
void sd_fs_unmount(const char *base_path, sdmmc_card_t *card);
esp_err_t sd_fs_list_dir(const char *base_path, const char *path);
esp_err_t sd_fs_read_file(const char *base_path, const char *path, void *dst, size_t dst_size, size_t *out_size);
int sd_fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size);
bool sd_has_retryable_errno(int err);
bool sd_error_needs_reinit(esp_err_t err);
void sd_log_cid(const sdmmc_cid_t *cid);
void sd_log_ocr(const sdmmc_card_t *card);
void sd_log_scr(const sdmmc_card_t *card);
void sdmmc_console_register_system_commands();
#if defined(CONFIG_IDF_TARGET_ESP32P4)
void sd_ldo_init();
#endif
+1400
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include "esp_err.h"
#include "main.h"
#define SFX_CHANNELS 32
#define SFX_VOLUME_MAX 255
#define SFX_PAN_CENTER 128
#define SFX_PITCH_ONE 0x0100
typedef struct
{
const u8 *data;
u32 data_size;
u32 frame_count;
u32 sample_rate;
u16 block_align;
u16 bits_per_sample;
u8 channels;
u8 is_signed;
} SFX_WAV_INFO;
typedef struct
{
u8 active;
u8 handle;
u8 group;
u8 group_order;
u8 volume;
u8 pan;
u16 pitch;
float position;
SFX_WAV_INFO wav;
} SFX_CHANNEL;
typedef struct
{
u8 active;
u8 handle;
u8 group;
u8 group_order;
u8 volume;
u8 pan;
u16 pitch;
u32 sample_rate;
u32 frame_count;
u32 position;
u16 bits_per_sample;
u8 channels;
u8 is_signed;
} SFX_CHANNEL_STATE;
extern SFX_CHANNEL sfx_channels[SFX_CHANNELS];
extern u8 sfx_global_volume;
void sfx_init();
esp_err_t sfx_parse_wav_object(int handle, SFX_WAV_INFO *info);
esp_err_t sfx_load_file(const char *path, int *out_handle, SFX_WAV_INFO *out_info);
esp_err_t sfx_play(int handle, u8 group, u8 volume, u8 pan, u16 pitch, int *out_channel);
esp_err_t sfx_stop(int channel);
esp_err_t sfx_stop_group(u8 group);
esp_err_t sfx_stop_handle(int handle);
esp_err_t sfx_set_params(int channel, u8 volume, u8 pan, u16 pitch);
esp_err_t sfx_set_volume(u8 volume);
esp_err_t sfx_get_state(int channel, SFX_CHANNEL_STATE *state);
esp_err_t sfx_get_state_snapshot(int channel, SFX_CHANNEL_STATE *state);
esp_err_t sfx_play_sync(int handle, u8 group, u8 volume, u8 pan, u16 pitch, int *out_channel);
esp_err_t sfx_stop_sync(int channel);
esp_err_t sfx_stop_group_sync(u8 group);
esp_err_t sfx_stop_handle_sync(int handle);
esp_err_t sfx_set_params_sync(int channel, u8 volume, u8 pan, u16 pitch);
esp_err_t sfx_set_volume_sync(u8 volume);
esp_err_t sfx_get_state_sync(int channel, SFX_CHANNEL_STATE *state);
void sfx_render(float *mix, int sample_count, int output_sample_rate);
void sfx_console_register_system_commands();
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "freertos/FreeRTOS.h"
#define DMA_BUF_SIZE 16384
enum
{
DREQ_WSCAN,
DREQ_DATA,
DREQ_ZIP,
DREQ_RND,
DREQ_URL,
DREQ_STREAM,
DREQ_TRACK_STREAM,
DREQ_VFS_REQUEST,
DREQ_VFS_RESULT,
};
u8 rd_reg8(u8 reg);
u32 rd_reg32(u8 reg);
void rd_regs(u8 reg, const void *data, int size);
void wr_reg8(u8 reg, u8 val);
void wr_reg32(u8 reg, u32 val);
void wr_regs(u8 reg, const void *data, int size);
void init_spi_configs();
void init_slave_hd();
esp_err_t spi_switch_to_master();
esp_err_t spi_switch_to_slave();
void spi_master_set_clock_hz(u32 hz);
u32 spi_master_get_actual_freq_hz();
esp_err_t spi_master_set_data_lines(u8 lines);
esp_err_t spi_master_write_buf(u8 cmd, u16 addr, const void *tx_data, size_t size);
esp_err_t spi_master_read_buf(u8 cmd, u16 addr, void *rx_data, size_t size);
esp_err_t spi_master_xfer(void *tx_data, void *rx_data, size_t size);
esp_err_t spi_master_write_inline(u8 cmd, u16 addr, u32 value, size_t size);
esp_err_t spi_master_read_inline(u8 cmd, u16 addr, u32 *value, size_t size);
esp_err_t spi_master_host_cmd_inline(u8 cmd, u8 param);
bool spi_master_bg_is_busy();
bool spi_master_bg_is_done();
esp_err_t spi_master_bg_get_result();
esp_err_t spi_master_bg_wait_done(TickType_t ticks_to_wait);
esp_err_t spi_master_write_buf_bg(u8 cmd, u16 addr, const void *tx_data, size_t size);
esp_err_t spi_master_read_buf_bg(u8 cmd, u16 addr, void *rx_data, size_t size);
void spi_master_bg_task(void *arg);
void command();
void sender_task(void *arg);
void receiver_task(void *arg);
void put_rxq(int type);
void put_txq(int type);
u32 prepare_tx_data(u8 type, size_t size);
void process_rx_data(u8 type, size_t size);
void set_status(u8 err);
u8* get_dma_buf(void);
void spi_slave_dma_send_direct(size_t len);
void wait_status(u8 status);
void wait_not_status(u8 status);
+80
View File
@@ -0,0 +1,80 @@
#include <limits.h>
#include "esp_timer.h"
#include "stats.h"
namespace stats
{
STATS_t _st = {};
int64_t start_t;
u32 last_cmd_exec_time;
void reset_audio_render()
{
_st.audio_tracker_last_us = 0;
_st.audio_tracker_min_us = INT_MAX;
_st.audio_tracker_max_us = 0;
_st.audio_tracker_last_cpu_x10 = 0;
_st.audio_tracker_min_cpu_x10 = INT_MAX;
_st.audio_tracker_max_cpu_x10 = 0;
_st.audio_opl_last_us = 0;
_st.audio_opl_min_us = INT_MAX;
_st.audio_opl_max_us = 0;
_st.audio_opl_last_cpu_x10 = 0;
_st.audio_opl_min_cpu_x10 = INT_MAX;
_st.audio_opl_max_cpu_x10 = 0;
_st.audio_sfx_last_us = 0;
_st.audio_sfx_min_us = INT_MAX;
_st.audio_sfx_max_us = 0;
_st.audio_sfx_last_cpu_x10 = 0;
_st.audio_sfx_min_cpu_x10 = INT_MAX;
_st.audio_sfx_max_cpu_x10 = 0;
_st.audio_total_last_us = 0;
_st.audio_total_min_us = INT_MAX;
_st.audio_total_max_us = 0;
_st.audio_total_last_cpu_x10 = 0;
_st.audio_total_min_cpu_x10 = INT_MAX;
_st.audio_total_max_cpu_x10 = 0;
}
void init()
{
_st.drq_data_start_min_us = INT_MAX;
_st.drq_data_end_min_us = INT_MAX;
reset_audio_render();
}
void update_audio_render(int *last_us, int *min_us, int *max_us, int *last_cpu_x10, int *min_cpu_x10, int *max_cpu_x10, int us, int sample_rate, int sample_count)
{
int cpu_x10 = 0;
if (sample_count > 0)
cpu_x10 = (int)((int64_t)us * sample_rate * 10 / sample_count / 10000);
*last_us = us;
if (us < *min_us) *min_us = us;
if (us > *max_us) *max_us = us;
*last_cpu_x10 = cpu_x10;
if (cpu_x10 < *min_cpu_x10) *min_cpu_x10 = cpu_x10;
if (cpu_x10 > *max_cpu_x10) *max_cpu_x10 = cpu_x10;
}
void set_start()
{
start_t = esp_timer_get_time();
}
void set_end()
{
last_cmd_exec_time = esp_timer_get_time() - start_t;
}
u32 get_time()
{
return last_cmd_exec_time;
}
};
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "main.h"
typedef struct
{
int drq_data_t;
int drq_data_start_last_us;
int drq_data_start_min_us;
int drq_data_start_max_us;
int drq_data_end_last_us;
int drq_data_end_min_us;
int drq_data_end_max_us;
int audio_tracker_last_us;
int audio_tracker_min_us;
int audio_tracker_max_us;
int audio_tracker_last_cpu_x10;
int audio_tracker_min_cpu_x10;
int audio_tracker_max_cpu_x10;
int audio_opl_last_us;
int audio_opl_min_us;
int audio_opl_max_us;
int audio_opl_last_cpu_x10;
int audio_opl_min_cpu_x10;
int audio_opl_max_cpu_x10;
int audio_sfx_last_us;
int audio_sfx_min_us;
int audio_sfx_max_us;
int audio_sfx_last_cpu_x10;
int audio_sfx_min_cpu_x10;
int audio_sfx_max_cpu_x10;
int audio_total_last_us;
int audio_total_min_us;
int audio_total_max_us;
int audio_total_last_cpu_x10;
int audio_total_min_cpu_x10;
int audio_total_max_cpu_x10;
float xm_samp_min;
float xm_samp_max;
char runtime_stats_buffer[2048];
} STATS_t;
namespace stats
{
extern STATS_t _st;
void init();
void reset_audio_render();
void update_audio_render(int *last_us, int *min_us, int *max_us, int *last_cpu_x10, int *min_cpu_x10, int *max_cpu_x10, int us, int sample_rate, int sample_count);
void set_start();
void set_end();
u32 get_time();
};
+502
View File
@@ -0,0 +1,502 @@
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "helper.h"
#include "stream_client.h"
static const char * TAG = "stream";
#define RING_BUF_SIZE (64 * 1024)
static uint8_t * ring_buf = NULL;
static volatile uint32_t ring_head = 0;
static volatile uint32_t ring_tail = 0;
static volatile bool ring_eof = false;
static SemaphoreHandle_t ring_mutex = NULL;
static TaskHandle_t reader_task_handle = NULL;
static int stream_sock = -1;
static esp_http_client_handle_t stream_http = NULL;
static uint32_t stream_total_size = 0;
static uint32_t stream_read_size = 0;
static bool stream_is_gopher = false;
static void reader_task(void *arg);
static uint32_t ring_available(void)
{
uint32_t h = ring_head;
uint32_t t = ring_tail;
if (h >= t) return h - t;
return RING_BUF_SIZE - t + h;
}
static uint32_t ring_free(void)
{
return RING_BUF_SIZE - ring_available() - 1;
}
static esp_err_t stream_ring_alloc(void)
{
if (ring_buf) return ESP_OK;
ring_buf = (uint8_t*)heap_caps_malloc(RING_BUF_SIZE, MALLOC_CAP_SPIRAM);
if (!ring_buf)
{
ESP_LOGE(TAG, "ring buffer alloc failed");
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "ring buffer allocated: %p", ring_buf);
return ESP_OK;
}
static void stream_ring_free(void)
{
if (!ring_buf) return;
heap_caps_free(ring_buf);
ring_buf = NULL;
ESP_LOGI(TAG, "ring buffer freed");
}
static void stream_ring_reset(void)
{
xSemaphoreTake(ring_mutex, portMAX_DELAY);
ring_head = 0;
ring_tail = 0;
ring_eof = false;
xSemaphoreGive(ring_mutex);
}
static void stream_transport_close(void)
{
if (stream_http)
{
esp_http_client_close(stream_http);
esp_http_client_cleanup(stream_http);
stream_http = NULL;
}
if (stream_sock >= 0)
{
close(stream_sock);
stream_sock = -1;
}
}
static esp_err_t stream_reader_start(void)
{
esp_err_t err = stream_ring_alloc();
if (err != ESP_OK) return err;
stream_ring_reset();
stream_read_size = 0;
if (xTaskCreatePinnedToCoreWithCaps(reader_task, "stream_reader", 4096, NULL, STREAMER_TASK_PRIO, &reader_task_handle, 0, task_ram_type_non_critical) == pdPASS)
return ESP_OK;
stream_ring_free();
return ESP_FAIL;
}
static void stream_state_reset(void)
{
stream_total_size = 0;
stream_read_size = 0;
stream_is_gopher = false;
stream_ring_reset();
}
static void reader_task(void *arg)
{
uint8_t temp_buf[512];
while (true)
{
int len = 0;
if (stream_http)
{
len = esp_http_client_read(stream_http, (char*)temp_buf, sizeof(temp_buf));
}
else if (stream_sock >= 0)
{
len = read(stream_sock, temp_buf, sizeof(temp_buf));
}
if (len <= 0)
{
xSemaphoreTake(ring_mutex, portMAX_DELAY);
ring_eof = true;
xSemaphoreGive(ring_mutex);
ESP_LOGI(TAG, "reader: EOF, total=%u", stream_read_size);
break;
}
stream_read_size += len;
// Wait for space in ring buffer
while (true)
{
xSemaphoreTake(ring_mutex, portMAX_DELAY);
uint32_t free = ring_free();
xSemaphoreGive(ring_mutex);
if (free >= (uint32_t) len) break;
vTaskDelay(1);
}
// Write to ring buffer
xSemaphoreTake(ring_mutex, portMAX_DELAY);
uint32_t h = ring_head;
for(int i = 0; i < len; i++)
{
ring_buf[h] = temp_buf[i];
h = (h + 1) % RING_BUF_SIZE;
}
ring_head = h;
xSemaphoreGive(ring_mutex);
}
reader_task_handle = NULL;
vTaskDelete(NULL);
}
void stream_init(void)
{
if (!ring_mutex)
ring_mutex = xSemaphoreCreateMutexWithCaps(task_ram_type_non_critical);
stream_sock = -1;
stream_http = NULL;
reader_task_handle = NULL;
stream_state_reset();
ESP_LOGI(TAG, "init done");
}
static void stream_http_common(bool use_https)
{
ESP_LOGI(TAG, "STREAM_%s_START", use_https ? "HTTPS" : "HTTP");
stream_state_reset();
if (!net.url[0])
{
ESP_LOGW(TAG, "URL not set");
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
char * url = (char*)net.url;
const char * prefix = use_https ? "https://" : "http://";
int prefix_len = use_https ? 8 : 7;
if (strncmp(url, prefix, prefix_len) != 0)
{
ESP_LOGW(TAG, "invalid URL format");
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
ESP_LOGI(TAG, "GET %s", url);
esp_http_client_config_t cfg = {};
cfg.url = url;
cfg.timeout_ms = 30000;
cfg.buffer_size = 512;
cfg.user_agent = "ZiFi/1.0";
if (use_https)
cfg.crt_bundle_attach = esp_crt_bundle_attach;
stream_http = esp_http_client_init( & cfg);
if (!stream_http)
{
ESP_LOGW(TAG, "init fail");
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
esp_err_t err = esp_http_client_open(stream_http, 0);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "open fail");
esp_http_client_cleanup(stream_http);
stream_http = NULL;
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
esp_http_client_fetch_headers(stream_http);
int code = esp_http_client_get_status_code(stream_http);
ESP_LOGI(TAG, "status %d", code);
if (code < 200 || code >= 300)
{
esp_http_client_close(stream_http);
esp_http_client_cleanup(stream_http);
stream_http = NULL;
wr_reg8(ESP_REG_STATUS, 0xB3);
return;
}
int content_length = esp_http_client_get_content_length(stream_http);
if (content_length < 0)
stream_total_size = 0xFFFFFFFF;
else
stream_total_size = (uint32_t) content_length;
ESP_LOGI(TAG, "connected, size=%u", stream_total_size);
if (stream_reader_start() != ESP_OK)
{
stream_transport_close();
wr_reg8(ESP_REG_STATUS, 0xB0);
return;
}
wr_reg32(ESP_REG_DATA_SIZE, stream_total_size);
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
}
void stream_http_start(void)
{
stream_http_common(false);
}
void stream_https_start(void)
{
stream_http_common(true);
}
void stream_gopher_start(void)
{
ESP_LOGI(TAG, "STREAM_GOPHER_START");
stream_state_reset();
stream_is_gopher = true;
if (!net.url[0])
{
ESP_LOGW(TAG, "URL not set");
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
char * url = (char*)net.url;
if (strncmp(url, "gopher://", 9) != 0)
{
ESP_LOGW(TAG, "invalid URL format");
wr_reg8(ESP_REG_STATUS, 0x81);
return;
}
ESP_LOGI(TAG, "GET %s", url);
char host[256];
int port = 70;
char selector[256];
memset(host, 0, sizeof(host));
memset(selector, 0, sizeof(selector));
//strcpy(selector, "/");
selector[0] = '\0';
const char * p = url + 9;
const char * slash = strchr(p, '/');
const char * colon = strchr(p, ':');
if (colon && (!slash || colon < slash))
{
int host_len = colon - p;
strncpy(host, p, host_len);
host[host_len] = 0;
port = atoi(colon + 1);
if (slash)
{
const char * sel_start = slash + 1;
// Skip Gopher type character and slash if present (e.g., "9/" -> "")
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
sel_start += 2;
strncpy(selector, sel_start, sizeof(selector) - 1);
}
}
else if (slash)
{
int host_len = slash - p;
strncpy(host, p, host_len);
host[host_len] = 0;
const char * sel_start = slash + 1;
// Skip Gopher type character and slash if present
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
sel_start += 2;
strncpy(selector, sel_start, sizeof(selector) - 1);
}
else
strncpy(host, p, sizeof(host) - 1);
struct hostent * server = gethostbyname(host);
if (!server)
{
ESP_LOGW(TAG, "DNS lookup failed");
wr_reg8(ESP_REG_STATUS, 0xB1);
return;
}
stream_sock = socket(AF_INET, SOCK_STREAM, 0);
if (stream_sock < 0)
{
ESP_LOGW(TAG, "socket creation failed");
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
if (connect(stream_sock, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) < 0)
{
ESP_LOGW(TAG, "connection failed");
close(stream_sock);
stream_sock = -1;
wr_reg8(ESP_REG_STATUS, 0xB2);
return;
}
// Set socket timeout to 30 seconds
struct timeval timeout;
timeout.tv_sec = 30;
timeout.tv_usec = 0;
setsockopt(stream_sock, SOL_SOCKET, SO_RCVTIMEO, & timeout, sizeof(timeout));
char request[512];
snprintf(request, sizeof(request), "/%s\r\n", selector);
write(stream_sock, request, strlen(request));
ESP_LOGI(TAG, "sent selector: [%s]", selector);
// Size unknown from Gopher
stream_total_size = 0xFFFFFFFF;
ESP_LOGI(TAG, "connected");
if (stream_reader_start() != ESP_OK)
{
stream_transport_close();
wr_reg8(ESP_REG_STATUS, 0xB0);
return;
}
wr_reg32(ESP_REG_DATA_SIZE, stream_total_size);
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
}
void stream_read(void)
{
if (stream_sock < 0 && !stream_http)
{
ESP_LOGW(TAG, "stream not opened");
wr_reg8(ESP_REG_STATUS, 0x82);
return;
}
// Wait for data in ring buffer or EOF
int timeout = 0;
while (timeout < 30000)
{
xSemaphoreTake(ring_mutex, portMAX_DELAY);
uint32_t avail = ring_available();
bool eof = ring_eof;
xSemaphoreGive(ring_mutex);
if (avail > 0 || eof) break;
vTaskDelay(1);
timeout++;
}
xSemaphoreTake(ring_mutex, portMAX_DELAY);
uint32_t avail = ring_available();
bool eof = ring_eof;
if (avail == 0 && eof)
{
xSemaphoreGive(ring_mutex);
ESP_LOGI(TAG, "stream end, total=%u", stream_read_size);
wr_reg32(ESP_REG_DATA_SIZE, 0);
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
return;
}
if (avail == 0)
{
xSemaphoreGive(ring_mutex);
ESP_LOGW(TAG, "timeout waiting for data");
wr_reg32(ESP_REG_DATA_SIZE, 0);
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
return;
}
// Read up to 512 bytes from ring buffer
uint32_t to_read = avail > 512 ? 512 : avail;
uint8_t * dma = get_dma_buf();
uint32_t t = ring_tail;
for (uint32_t i = 0; i < to_read; i++)
{
dma[i] = ring_buf[t];
t = (t + 1) % RING_BUF_SIZE;
}
ring_tail = t;
xSemaphoreGive(ring_mutex);
spi_slave_dma_send_direct(to_read);
}
void stream_close(void)
{
if (reader_task_handle)
{
vTaskDelete(reader_task_handle);
reader_task_handle = NULL;
}
stream_transport_close();
ESP_LOGI(TAG, "stream closed, total read=%u", stream_read_size);
stream_state_reset();
stream_ring_free();
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
void stream_init(void);
void stream_http_start(void);
void stream_https_start(void);
void stream_gopher_start(void);
void stream_read(void);
void stream_close(void);
+1917
View File
File diff suppressed because it is too large Load Diff
+13366
View File
File diff suppressed because it is too large Load Diff
+932
View File
@@ -0,0 +1,932 @@
#pragma once
#include "esp_attr.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "esp_err.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
// ============================================================================
// Common tracker declarations
// ============================================================================
// -------------------- Common tracker types --------------------
typedef enum tracker_format_e
{
TRACKER_FORMAT_XM = 0,
TRACKER_FORMAT_MOD,
TRACKER_FORMAT_S3M
} tracker_format_t;
#define TRACKER_CONTEXT_SEGMENT_PAGE_CAPACITY 64
#define TRACKER_CONTEXT_SEGMENTS_MAGIC 0x5453474dU
#define TRACKER_CONTEXT_SEGMENTS_MAGIC_CHECK 0xaba7b8b2U
typedef enum tracker_context_segment_type_e
{
TRACKER_CONTEXT_SEG_CONTEXT = 0,
TRACKER_CONTEXT_SEG_RUNTIME,
TRACKER_CONTEXT_SEG_PATTERNS,
TRACKER_CONTEXT_SEG_INSTRUMENTS,
TRACKER_CONTEXT_SEG_SAMPLES,
TRACKER_CONTEXT_SEG_FORMAT_EXTRA
} tracker_context_segment_type_t;
typedef struct tracker_context_segment_s
{
void *addr;
size_t size;
uint8_t type;
uint8_t flags;
uint16_t index;
} tracker_context_segment_t;
typedef struct tracker_context_segment_page_s tracker_context_segment_page_t;
// -------------------- Common byte order helpers --------------------
uint16_t tracker_rd_be16(const uint8_t *p);
uint16_t tracker_rd_le16(const uint8_t *p);
uint32_t tracker_rd_le24(const uint8_t *p);
uint32_t tracker_rd_le32(const uint8_t *p);
// -------------------- Common size helpers --------------------
int tracker_size_add(size_t *dst, size_t add);
int tracker_size_mul(size_t a, size_t b, size_t *out);
// -------------------- Common string/memory helpers --------------------
void tracker_copy_trimmed(char *dst, size_t dst_size, const uint8_t *src, size_t src_size);
void tracker_memcpy_pad(void *dst, size_t dst_len, const void *src, size_t src_len, size_t offset);
// -------------------- XM format helpers --------------------
// -------------------- XM grouped context allocation helpers --------------------
typedef struct xm_context_group_sizes_s
{
size_t context_size;
size_t patterns_size;
size_t instruments_size;
size_t samples_size;
size_t runtime_size;
size_t format_extra_size;
size_t total_size;
} xm_context_group_sizes_t;
typedef struct xm_context_group_cursor_s
{
char *patterns;
char *patterns_end;
char *instruments;
char *instruments_end;
char *samples;
char *samples_end;
char *runtime;
char *runtime_end;
char *format_extra;
char *format_extra_end;
} xm_context_group_cursor_t;
// -------------------- MOD format helpers --------------------
// -------------------- S3M format helpers --------------------
// ============================================================================
// XM public API
// ============================================================================
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
/* Contributor: Dan Spencer <dan@atomicpotato.net> */
/* This program is free software. It comes without any warranty, to the
* extent permitted by applicable law. You can redistribute it and/or
* modify it under the terms of the Do What The Fuck You Want To Public
* License, Version 2, as published by Sam Hocevar. See
* http://sam.zoy.org/wtfpl/COPYING for more details. */
#define XM_DEFENSIVE 1
#define XM_LIBXMIZE_DELTA_SAMPLES 0
#define XM_DEBUG 0
#define XM_LINEAR_INTERPOLATION 1
#define XM_RAMPING 1
#define XM_STRINGS 1
struct xm_context_s;
typedef struct xm_context_s xm_context_t;
typedef struct s3m_adlib_instrument_s
{
uint8_t type;
uint8_t volume;
uint32_t c4speed;
uint8_t regs[12];
} s3m_adlib_instrument_t;
typedef void* (*MFUNC)(size_t);
/** Create a XM context.
*
* @param moddata the contents of the module
* @param moddata_length the length of the contents of the module, in bytes
* @param rate play rate in Hz, recommended value of 48000
*
* @returns size on success
* @returns -1 if module data is not sane
* @returns -2 if memory allocation failed
*/
int xm_create_context_safe(xm_context_t**, void* moddata, size_t moddata_length, uint32_t rate, MFUNC mfunc);
/** Create a XM context.
*
* This function will produce smaller code size compared to
* xm_create_context(), but requires converting the .xm file to a
* non-portable format beforehand.
*
* This function doesn't do any kind of error checking.
*
* @param libxmizeddata: pointer to module data, needs to point to a
* writable area, freeing it is equivalent as calling
* xm_free_context().
*
* @see xm_create_context()
*/
void xm_create_context_from_libxmize(xm_context_t**, char* libxmizeddata, uint32_t rate);
/** Free a XM context created by xm_create_context(). */
void xm_free_context(xm_context_t*);
int tracker_context_register_segment(xm_context_t *ctx, void *addr, size_t size, uint8_t type);
int tracker_context_has_registered_segments(const xm_context_t *ctx);
uint16_t tracker_context_get_segment_count(const xm_context_t *ctx);
int tracker_context_get_segment(const xm_context_t *ctx, uint16_t index, tracker_context_segment_t *segment);
void tracker_context_free_segments(xm_context_t *ctx);
void xm_sample(xm_context_t* ctx, float* p_left, float* p_right);
void xm_render(xm_context_t* ctx, float* out, size_t samples);
/** Set the maximum number of times a module can loop. After the
* specified number of loops, calls to xm_generate_samples will only
* generate silence. You can control the current number of loops with
* xm_get_loop_count().
*
* @param loopcnt maximum number of loops. Use 0 to loop
* indefinitely. */
void xm_set_max_loop_count(xm_context_t*, uint8_t loopcnt);
/** Get the loop count of the currently playing module. This value is
* 0 when the module is still playing, 1 when the module has looped
* once, etc. */
uint8_t xm_get_loop_count(xm_context_t*);
/** Get the length, in samples, of one full pass through the module loaded
* into the given object handle (computed once at TRACK_INIT time). 0 if
* the handle has no track info yet. */
uint32_t xm_get_total_samples(int handle);
/** Seek to a specific position in a module.
*
* WARNING, WITH BIG LETTERS: seeking modules is broken by design,
* don't expect miracles.
*/
void xm_seek(xm_context_t*, uint8_t pot, uint8_t row, uint16_t tick);
/** Mute or unmute a channel.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*
* @return whether the channel was muted.
*/
bool xm_mute_channel(xm_context_t*, uint16_t, bool);
/** Mute or unmute an instrument.
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*
* @return whether the instrument was muted.
*/
bool xm_mute_instrument(xm_context_t*, uint16_t, bool);
/** Get the module name as a NUL-terminated string. */
const char* xm_get_module_name(xm_context_t*);
/** Get the tracker name as a NUL-terminated string. */
const char* xm_get_tracker_name(xm_context_t*);
/** Get the number of channels. */
uint16_t xm_get_number_of_channels(xm_context_t*);
/** Get the module length (in patterns). */
uint16_t xm_get_module_length(xm_context_t*);
/** Get the number of patterns. */
uint16_t xm_get_number_of_patterns(xm_context_t*);
/** Get the number of rows of a pattern.
*
* @note Pattern numbers go from 0 to
* xm_get_number_of_patterns(...)-1.
*/
uint16_t xm_get_number_of_rows(xm_context_t*, uint16_t);
/** Get the number of instruments. */
uint16_t xm_get_number_of_instruments(xm_context_t*);
/** Get the number of samples of an instrument.
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*/
uint16_t xm_get_number_of_samples(xm_context_t*, uint16_t);
/** Get the internal buffer for a given sample waveform.
*
* This buffer can be read from or written to, at any time, but the
* length cannot change. The buffer must be cast to (int8_t*) or
* (int16_t*) depending on the sample type.
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*
* @note Sample numbers go from 0 to
* xm_get_nubmer_of_samples(...,instr)-1.
*/
void* xm_get_sample_waveform(xm_context_t*, uint16_t instr, uint16_t sample, size_t* length, uint8_t* bits);
/** Get the current module speed.
*
* @param bpm will receive the current BPM
* @param tempo will receive the current tempo (ticks per line)
*/
void xm_get_playing_speed(xm_context_t*, uint16_t* bpm, uint16_t* tempo);
/** Get the current position in the module being played.
*
* @param pattern_index if not NULL, will receive the current pattern
* index in the POT (pattern order table)
*
* @param pattern if not NULL, will receive the current pattern number
*
* @param row if not NULL, will receive the current row
*
* @param samples if not NULL, will receive the total number of
* generated samples (divide by sample rate to get seconds of
* generated audio)
*/
void xm_get_position(xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples);
/** Get the latest time (in number of generated samples) when a
* particular instrument was triggered in any channel.
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*/
uint64_t xm_get_latest_trigger_of_instrument(xm_context_t*, uint16_t);
/** Get the latest time (in number of generated samples) when a
* particular sample was triggered in any channel.
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*
* @note Sample numbers go from 0 to
* xm_get_nubmer_of_samples(...,instr)-1.
*/
uint64_t xm_get_latest_trigger_of_sample(xm_context_t*, uint16_t instr, uint16_t sample);
/** Get the latest time (in number of generated samples) when any
* instrument was triggered in a given channel.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*/
uint64_t xm_get_latest_trigger_of_channel(xm_context_t*, uint16_t);
/** Checks whether a channel is active (ie: is playing something).
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*/
bool xm_is_channel_active(xm_context_t*, uint16_t);
/** Get the instrument number currently playing in a channel.
*
* @returns instrument number, or 0 if channel is not active.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*
* @note Instrument numbers go from 1 to
* xm_get_number_of_instruments(...).
*/
uint16_t xm_get_instrument_of_channel(xm_context_t*, uint16_t);
/** Get the frequency of the sample currently playing in a channel.
*
* @returns a frequency in Hz. If the channel is not active, return
* value is undefined.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*/
float xm_get_frequency_of_channel(xm_context_t*, uint16_t);
/** Get the volume of the sample currently playing in a channel. This
* takes into account envelopes, etc.
*
* @returns a volume between 0 or 1. If the channel is not active,
* return value is undefined.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*/
float xm_get_volume_of_channel(xm_context_t*, uint16_t);
/** Get the panning of the sample currently playing in a channel. This
* takes into account envelopes, etc.
*
* @returns a panning between 0 (L) and 1 (R). If the channel is not
* active, return value is undefined.
*
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
*/
float xm_get_panning_of_channel(xm_context_t*, uint16_t);
// ============================================================================
// Common tracker runtime internals
// ============================================================================
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
/* This program is free software. It comes without any warranty, to the
* extent permitted by applicable law. You can redistribute it and/or
* modify it under the terms of the Do What The Fuck You Want To Public
* License, Version 2, as published by Sam Hocevar. See
* http://sam.zoy.org/wtfpl/COPYING for more details. */
#if XM_DEBUG
#include <stdio.h>
#define DEBUG(fmt, ...) \
{ \
fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
fflush(stderr); \
}
#else
#define DEBUG(...)
#endif
#if XM_BIG_ENDIAN
#error "Big endian platforms are not yet supported, sorry"
/* Make sure the compiler stops, even if #error is ignored */
extern int __fail[-1];
#endif
/* ----- XM constants ----- */
#define SAMPLE_NAME_LENGTH 22
#define INSTRUMENT_NAME_LENGTH 22
#define MODULE_NAME_LENGTH 20
#define TRACKER_NAME_LENGTH 20
#define PATTERN_ORDER_TABLE_LENGTH 256
#define NUM_NOTES 96
#define NUM_ENVELOPE_POINTS 12
#define MAX_NUM_ROWS 256
#if XM_RAMPING
#define XM_SAMPLE_RAMPING_POINTS 32
#endif
/* ----- Data types ----- */
enum xm_waveform_type_e {
XM_SINE_WAVEFORM = 0,
XM_RAMP_DOWN_WAVEFORM = 1,
XM_SQUARE_WAVEFORM = 2,
XM_RANDOM_WAVEFORM = 3,
XM_RAMP_UP_WAVEFORM = 4,
};
typedef enum xm_waveform_type_e xm_waveform_type_t;
enum xm_loop_type_e {
XM_NO_LOOP,
XM_FORWARD_LOOP,
XM_PING_PONG_LOOP,
};
typedef enum xm_loop_type_e xm_loop_type_t;
enum xm_frequency_type_e {
XM_LINEAR_FREQUENCIES,
XM_AMIGA_FREQUENCIES,
XM_S3M_FREQUENCIES,
};
#define XM_EFFECT_S3M_SPEED 34
#define XM_EFFECT_S3M_TEMPO 35
#define XM_EFFECT_S3M_FINE_VIBRATO 36
#define XM_EFFECT_S3M_CHANNEL_VOLUME 37
#define XM_EFFECT_S3M_CHANNEL_VOLUME_SLIDE 38
#define XM_EFFECT_S3M_POSITION_JUMP 39
#define XM_EFFECT_S3M_PATTERN_BREAK 40
#define XM_EFFECT_S3M_VOLUME_SLIDE 41
#define XM_EFFECT_S3M_PORTAMENTO_DOWN 42
#define XM_EFFECT_S3M_PORTAMENTO_UP 43
#define XM_EFFECT_S3M_TONE_PORTAMENTO 44
#define XM_EFFECT_S3M_VIBRATO 45
#define XM_EFFECT_S3M_TREMOR 46
#define XM_EFFECT_S3M_ARPEGGIO 47
#define XM_EFFECT_S3M_VIBRATO_VOLUME_SLIDE 48
#define XM_EFFECT_S3M_TONE_PORTAMENTO_VOLUME_SLIDE 49
#define XM_EFFECT_S3M_SAMPLE_OFFSET 50
#define XM_EFFECT_S3M_PANNING_SLIDE 51
#define XM_EFFECT_S3M_RETRIG 52
#define XM_EFFECT_S3M_TREMOLO 53
#define XM_EFFECT_S3M_GLOBAL_VOLUME 54
#define XM_EFFECT_S3M_GLOBAL_VOLUME_SLIDE 55
#define XM_EFFECT_S3M_PANNING 56
#define XM_EFFECT_S3M_EXTENDED 57
#define XM_EFFECT_S3M_PANBRELLO 58
#define XM_EFFECT_S3M_MIDI_MACRO 59
#define XM_EFFECT_S3M_NONE 60
typedef enum xm_frequency_type_e xm_frequency_type_t;
struct xm_envelope_point_s {
uint16_t frame;
uint16_t value;
};
typedef struct xm_envelope_point_s xm_envelope_point_t;
struct xm_envelope_s {
xm_envelope_point_t points[NUM_ENVELOPE_POINTS];
uint8_t num_points;
uint8_t sustain_point;
uint8_t loop_start_point;
uint8_t loop_end_point;
bool enabled;
bool sustain_enabled;
bool loop_enabled;
};
typedef struct xm_envelope_s xm_envelope_t;
struct xm_sample_s {
#if XM_STRINGS
char name[SAMPLE_NAME_LENGTH + 1];
#endif
uint8_t bits; /* Either 8 or 16 */
uint32_t length;
uint32_t loop_start;
uint32_t loop_length;
uint32_t loop_end;
float volume;
int8_t finetune;
xm_loop_type_t loop_type;
float panning;
int8_t relative_note;
uint32_t c4speed; /* S3M C4Speed / OpenMPT c5speed field; used by S3M frequency mode. */
uint64_t latest_trigger;
union {
int8_t* data8;
int16_t* data16;
};
};
typedef struct xm_sample_s xm_sample_t;
struct xm_instrument_s {
#if XM_STRINGS
char name[INSTRUMENT_NAME_LENGTH + 1];
#endif
uint16_t num_samples;
uint8_t sample_of_notes[NUM_NOTES];
xm_envelope_t volume_envelope;
xm_envelope_t panning_envelope;
xm_waveform_type_t vibrato_type;
uint8_t vibrato_sweep;
uint8_t vibrato_depth;
uint8_t vibrato_rate;
uint16_t volume_fadeout;
uint64_t latest_trigger;
bool muted;
xm_sample_t* samples;
};
typedef struct xm_instrument_s xm_instrument_t;
struct xm_pattern_slot_s {
uint16_t period; /* MOD raw Amiga period; 0 means XM note-based slot */
uint8_t note; /* 1-96, 97 = Key Off note */
uint8_t instrument; /* 1-128 */
uint8_t volume_column;
uint8_t effect_type;
uint8_t effect_param;
};
typedef struct xm_pattern_slot_s xm_pattern_slot_t;
struct xm_pattern_s {
uint16_t num_rows;
xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */
};
typedef struct xm_pattern_s xm_pattern_t;
struct xm_mod_efx_backup_s {
xm_sample_t* sample;
int8_t* backup8;
uint32_t loop_start;
uint32_t loop_length;
};
typedef struct xm_mod_efx_backup_s xm_mod_efx_backup_t;
struct xm_module_s {
#if XM_STRINGS
char name[MODULE_NAME_LENGTH + 1];
char trackername[TRACKER_NAME_LENGTH + 1];
#endif
uint16_t length;
uint16_t restart_position;
uint16_t num_channels;
uint16_t num_patterns;
uint16_t num_instruments;
xm_frequency_type_t frequency_type;
uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
xm_pattern_t* patterns;
xm_instrument_t* instruments; /* Instrument 1 has index 0,
* instrument 2 has index 1, etc. */
};
typedef struct xm_module_s xm_module_t;
struct xm_channel_context_s {
float note;
float orig_note; /* The original note before effect modifications, as read in the pattern. */
xm_instrument_t* instrument; /* Could be NULL */
xm_sample_t* sample; /* Could be NULL */
xm_pattern_slot_t* current;
float sample_position;
float period;
float frequency;
float step;
bool ping; /* For ping-pong samples: true is -->, false is <-- */
float volume; /* Ideally between 0 (muted) and 1 (loudest) */
float channel_volume; /* S3M Mxx/Nxy channel global volume multiplier, 0..1. */
float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */
float default_panning; /* < 0: use sample panning; >= 0: use channel default panning */
bool surround; /* S3M XA4: inverse right channel phase around centered panning. */
tracker_format_t tracker_format;
float period_note_offset; /* MOD finetune offset for raw period slots */
uint16_t autovibrato_ticks;
bool sustained;
float fadeout_volume;
float volume_envelope_volume;
float panning_envelope_panning;
uint16_t volume_envelope_frame_count;
uint16_t panning_envelope_frame_count;
float autovibrato_note_offset;
bool arp_in_progress;
uint8_t arp_note_offset;
uint8_t arpeggio_param;
uint8_t volume_slide_param;
uint8_t fine_volume_slide_param;
uint8_t global_volume_slide_param;
uint8_t channel_volume_slide_param;
uint8_t panning_slide_param;
uint8_t portamento_up_param;
uint8_t portamento_down_param;
uint8_t fine_portamento_up_param;
uint8_t fine_portamento_down_param;
uint8_t extra_fine_portamento_up_param;
uint8_t extra_fine_portamento_down_param;
uint8_t tone_portamento_param;
float tone_portamento_target_period;
uint8_t multi_retrig_param;
uint8_t note_delay_param;
uint8_t sample_offset_param;
uint8_t s3m_high_sample_offset_param;
uint8_t invert_loop_speed;
uint16_t invert_loop_delay;
uint32_t invert_loop_offset;
uint8_t pattern_loop_origin; /* Where to restart a E6y loop */
uint8_t pattern_loop_count; /* How many loop passes have been done */
bool vibrato_in_progress;
xm_waveform_type_t vibrato_waveform;
bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */
uint8_t vibrato_param;
uint16_t vibrato_ticks; /* Position in the waveform */
float vibrato_note_offset;
float vibrato_period_offset;
xm_waveform_type_t tremolo_waveform;
bool tremolo_waveform_retrigger;
uint8_t tremolo_param;
uint8_t tremolo_ticks;
float tremolo_volume;
xm_waveform_type_t panbrello_waveform;
bool panbrello_waveform_retrigger;
bool panbrello_in_progress;
uint8_t panbrello_param;
uint16_t panbrello_ticks;
float panbrello_panning_offset;
uint8_t tremor_param;
uint8_t s3m_effect_memory;
bool s3m_glissando_control;
bool s3m_note_cut;
bool tremor_on;
uint8_t s3m_opl_key_on;
uint8_t s3m_opl_instrument;
uint32_t s3m_opl_trigger_id;
uint32_t s3m_opl_synced_trigger_id;
uint64_t latest_trigger;
bool muted;
#if XM_RAMPING
/* These values are updated at the end of each tick, to save
* a couple of float operations on every generated sample. */
float target_panning;
float target_volume;
unsigned long frame_count;
float end_of_previous_sample[XM_SAMPLE_RAMPING_POINTS];
#endif
float actual_panning;
float actual_volume;
};
typedef struct xm_channel_context_s xm_channel_context_t;
struct xm_context_s {
size_t ctx_size; /* Must be first, see xm_create_context_from_libxmize() */
xm_module_t module;
tracker_format_t tracker_format;
bool s3m_fast_volume_slides;
uint8_t s3m_tempo_slide_param;
uint16_t s3m_adlib_instrument_count;
s3m_adlib_instrument_t *s3m_adlib_instruments;
uint8_t *s3m_channel_opl;
uint32_t rate;
uint16_t tempo;
uint16_t bpm;
float global_volume;
float amplification;
#if XM_RAMPING
/* How much is a channel final volume allowed to change per
* sample; this is used to avoid abrubt volume changes which
* manifest as "clicks" in the generated sound. */
float volume_ramp;
float panning_ramp; /* Same for panning. */
#endif
uint8_t current_table_index;
uint8_t current_row;
uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */
float remaining_samples_in_tick;
uint64_t generated_samples;
bool position_jump;
bool pattern_break;
uint8_t jump_dest;
uint8_t jump_row;
/* Extra ticks to be played before going to the next row -
* Used for EEy effect */
uint16_t extra_ticks;
uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */
uint8_t loop_count;
uint8_t max_loop_count;
xm_channel_context_t* channels;
uint16_t mod_efx_backup_count;
xm_mod_efx_backup_t* mod_efx_backups;
int8_t* mod_efx_backup_data;
uint32_t tracker_segments_magic;
uint32_t tracker_segments_magic_check;
uint16_t tracker_segment_count;
tracker_context_segment_page_t* tracker_segment_pages;
};
/* ----- Internal API ----- */
/** Check the module data for errors/inconsistencies.
*
* @returns 0 if everything looks OK. Module should be safe to load.
*/
int xm_check_sanity_preload(const char*, size_t);
/** Check a loaded module for errors/inconsistencies.
*
* @returns 0 if everything looks OK.
*/
int xm_check_sanity_postload(xm_context_t*);
/** Get the number of bytes needed to store the module data in a
* dynamically allocated blank context.
*
* Things that are dynamically allocated:
* - sample data
* - sample structures in instruments
* - pattern data
* - row loop count arrays
* - pattern structures in module
* - instrument structures in module
* - channel contexts
* - context structure itself
* @returns 0 if everything looks OK.
*/
size_t xm_get_memory_needed_for_context(const char*, size_t);
/** Populate the context from module data.
*
* @returns non-zero on success
*/
int xm_load_module(xm_context_t*, const char*, size_t, xm_context_group_cursor_t*);
// ============================================================================
// MOD module support
// ============================================================================
#define MOD_TITLE_SIZE 20
#define MOD_SAMPLE_COUNT 31
#define MOD_15_SAMPLE_COUNT 15
#define MOD_15_SONG_LENGTH_OFFSET 470
#define MOD_15_RESTART_OFFSET 471
#define MOD_15_ORDER_OFFSET 472
#define MOD_15_HEADER_SIZE 600
#define MOD_SAMPLE_HEADER_SIZE 30
#define MOD_SONG_LENGTH_OFFSET 950
#define MOD_RESTART_OFFSET 951
#define MOD_ORDER_OFFSET 952
#define MOD_ORDER_COUNT 128
#define MOD_MAGIC_OFFSET 1080
#define MOD_HEADER_SIZE 1084
#define MOD_ROWS_PER_PATTERN 64
#define MOD_DEFAULT_TEMPO 6
#define MOD_DEFAULT_BPM 125
#define MOD_MAX_CHANNELS 32
struct xm_module_s;
struct xm_pattern_slot_s;
typedef struct
{
uint16_t channels;
uint16_t patterns;
uint16_t song_length;
uint16_t restart_position;
uint16_t sample_count;
size_t header_size;
size_t pattern_data_offset;
size_t sample_data_offset;
uint32_t sample_length[MOD_SAMPLE_COUNT];
uint32_t sample_loop_start[MOD_SAMPLE_COUNT];
uint32_t sample_loop_length[MOD_SAMPLE_COUNT];
uint32_t sample_file_length[MOD_SAMPLE_COUNT];
size_t pattern_bytes;
size_t sample_bytes;
size_t expected_size;
uint32_t efx_sample_mask;
uint16_t efx_backup_count;
size_t efx_backup_bytes;
} mod_layout_t;
int mod_create_context_safe(xm_context_t **ctxp, void *moddata, size_t moddata_length, uint32_t rate, MFUNC mfunc, size_t *out_bytes_needed);
int mod_check_sanity_preload(const void *moddata, size_t moddata_length);
uint16_t mod_get_channel_count(const void *moddata, size_t moddata_length);
int mod_read_layout_from_header(const uint8_t *header, size_t header_length, size_t moddata_length, mod_layout_t *layout);
size_t mod_get_memory_needed_for_layout(const mod_layout_t *layout);
int mod_layout_set_efx_backup_mask(mod_layout_t *layout, uint32_t sample_mask);
int mod_layout_set_efx_backup_all_looped(mod_layout_t *layout);
uint32_t mod_scan_efx_sample_mask_from_context(const xm_context_t *ctx, const mod_layout_t *layout);
uint32_t mod_scan_efx_sample_mask_from_data(const void *moddata, const mod_layout_t *layout);
int mod_get_group_sizes_for_layout(const mod_layout_t *layout, xm_context_group_sizes_t *sizes);
int mod_setup_module_header(xm_context_t *ctx, const uint8_t *header, const mod_layout_t *layout, xm_context_group_cursor_t *cursor);
int mod_setup_sample_metadata(xm_context_t *ctx, const uint8_t *header, const mod_layout_t *layout, uint16_t i, xm_context_group_cursor_t *cursor, MFUNC mfunc);
void mod_decode_pattern_slot(struct xm_pattern_slot_s *slot, const uint8_t *entry);
int mod_setup_context_runtime(xm_context_t *ctx, xm_context_group_cursor_t *cursor, uint32_t rate);
int mod_setup_efx_backups(xm_context_t *ctx, const mod_layout_t *layout, xm_context_group_cursor_t *cursor);
void mod_restore_efx_backups(xm_context_t *ctx);
int mod_finish_context(xm_context_t **ctxp, xm_context_t *ctx);
// ============================================================================
// S3M module support
// ============================================================================
#define S3M_HEADER_SIZE 96
#define S3M_MAGIC_OFFSET 44
#define S3M_MIN_CHANNELS 1
#define S3M_MAX_CHANNELS 32
#define S3M_MAX_ORDERS 256
#define S3M_MAX_SAMPLES 99
#define S3M_MAX_PATTERNS 256
#define S3M_SAMPLE_HEADER_SIZE 80
#define S3M_ROWS_PER_PATTERN 64
#define S3M_DEFAULT_C4SPEED 8363
int s3m_create_context_safe(xm_context_t **ctxp, void *s3mdata, size_t s3mdata_length, uint32_t rate, MFUNC mfunc, size_t *out_bytes_needed);
const char *s3m_get_last_error();
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
// ============================================================================
// ESP32 tracker task / CLI integration
// ============================================================================
enum
{
TRACK_TASK_INIT,
TRACK_TASK_PLAY,
TRACK_TASK_STOP,
TRACK_TASK_RESET,
TRACK_TASK_SET_POS,
TRACK_TASK_SET_PARAMS,
};
typedef struct
{
uint8_t task;
int handle;
} TRACK_TASK;
// Records the last time a TRACK_PLAY/RESET/SET_PARAMS command was rejected with
// ESP_ERR_INV_XM_HANDLE, since that check runs in ISR context (spi_slave.cpp)
// where ESP_LOGx isn't safe to call. Inspect via the "xm lasterr" console command.
typedef struct
{
volatile bool valid;
volatile uint8_t cmd;
volatile int handle;
volatile bool handle_in_range;
volatile uint8_t obj_type;
volatile int64_t timestamp_us; // esp_timer_get_time() at rejection - directly comparable to xm_task's own TIMING logs
} TrackHandleErrorInfo;
extern TrackHandleErrorInfo g_track_handle_error;
void record_track_handle_error(uint8_t cmd, int handle);
typedef struct
{
uint8_t task;
xm_context_t *ctx;
} PLAYER_TASK;
extern QueueHandle_t xm_queue;
extern int master_volume;
extern int mix_volume;
extern int curr_xm_handle;
int xm_cmd(int argc, char **argv);
void xm_task(void *arg);
void initialize_xm();
void xm_console_register_system_commands();
// Pause/resume XM tracker playback AND the shared i2s_task/player_task
// pipeline it renders through (also used by SFX and OPL, see their
// player_task usage) - used while the SMB2/3 server is active.
void tracker_suspend();
void tracker_resume();
int xm_load_play_file(const char *path, bool quiet = false);
int xm_play_cmd(int handle, bool quiet = false);
int xm_stop_cmd(bool quiet = false);
int xm_reset_cmd(int handle, bool quiet = false);
esp_err_t xm_host_stream_prepare_command(size_t module_size, size_t rx_size);
esp_err_t mod_host_stream_prepare_command(size_t module_size, size_t rx_size);
esp_err_t s3m_host_stream_prepare_command(size_t module_size, size_t rx_size);
void xm_host_stream_abort_current();
void xm_host_stream_process_rx_data(const uint8_t *data, size_t size);
#endif
+1195
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "tsspiffs.h"
#define TSF_PART_LABEL "tsf"
#define TSF_BASE_PATH "/tsf"
#define TSF_PART_SUBTYPE_VALUE 0x84
#define TSF_PART_SUBTYPE_NAME "tsf"
#define TSF_BLOCK_SIZE 4096
#define TSF_HAL_BUF_SIZE 256
#define TSF_VFS_MAX_FILES 4
extern bool tsf_storage_initialized;
extern bool tsf_storage_mounted;
extern char tsf_storage_base_path[32];
extern char tsf_storage_part_label[16];
esp_err_t tsf_storage_mount();
esp_err_t tsf_storage_mount_quiet();
esp_err_t tsf_storage_format();
esp_err_t tsf_storage_delete();
esp_err_t tsf_storage_init();
esp_err_t tsf_storage_ensure_ready();
esp_err_t tsf_storage_ensure_ready_quiet();
void tsf_storage_deinit();
TSF_VOLUME *tsf_storage_volume();
esp_err_t tsf_storage_space_info(uint64_t *total_bytes, uint64_t *free_bytes);
bool tsf_storage_path_has_base(const char *path);
esp_err_t tsf_storage_name_from_path(const char *path, char *out, size_t out_size);
bool tsf_storage_path_is_root(const char *path);
esp_err_t tsf_storage_open_read(const char *path, TSF_FILE *file, size_t *out_size);
esp_err_t tsf_storage_read_file(const char *path, void *dst, size_t size, size_t *out_size);
esp_err_t tsf_storage_list_dir(const char *path);
esp_err_t tsf_storage_result_to_err(TSF_RESULT res);
const char *tsf_storage_result_name(TSF_RESULT res);
const char *tsf_storage_subtype_name();
esp_err_t tsf_storage_register_vfs();
int tsf_storage_info_cmd(int argc, char **argv);
int tsf_storage_format_cmd(int argc, char **argv);
int tsf_storage_delete_cmd(int argc, char **argv);
int tsf_storage_ls_cmd(int argc, char **argv);
int tsf_storage_cmd(int argc, char **argv);
void tsf_console_register_system_commands();
+517
View File
@@ -0,0 +1,517 @@
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include "tsspiffs.h"
TSF_RESULT tsf_format(TSF_CONFIG *cfg)
{
if (!cfg || !cfg->block_size || !cfg->bulk_size) return TSF_RES_FS_ERROR;
if ((cfg->bulk_size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
if (cfg->block_size <= sizeof(TSF_CHUNK)) return TSF_RES_FS_ERROR;
if (!cfg->hal_read_func || !cfg->hal_write_func) return TSF_RES_FS_ERROR;
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
return tsf_init_chunk_range(cfg, cfg->bulk_start, cfg->bulk_size);
}
TSF_RESULT tsf_init_chunk(TSF_CONFIG *cfg, u32 addr)
{
if (!cfg) return TSF_RES_FS_ERROR;
return tsf_init_chunk_range(cfg, addr, cfg->block_size);
}
TSF_RESULT tsf_init_erased_chunk(TSF_CONFIG *cfg, u32 addr)
{
if (!cfg || !cfg->block_size) return TSF_RES_FS_ERROR;
if (!cfg->hal_write_func) return TSF_RES_FS_ERROR;
#ifdef TSF_CHECK_BLANK
TSF_RESULT rc = tsf_check_blank(cfg, addr, cfg->block_size);
if (rc != TSF_RES_OK)
return rc;
#endif
u32 magic = TSF_MAGIC;
cfg->hal_write_func(addr, &magic, sizeof(magic));
return TSF_RES_OK;
}
TSF_RESULT tsf_init_chunk_range(TSF_CONFIG *cfg, u32 addr, u32 size)
{
TSF_RESULT rc;
if (!cfg || !cfg->block_size || !size) return TSF_RES_FS_ERROR;
if ((size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
if (cfg->hal_erase_range_func)
{
rc = cfg->hal_erase_range_func(addr, size);
if (rc != TSF_RES_OK)
return rc;
}
else
{
for (u32 pos = 0; pos < size; pos += cfg->block_size)
{
rc = cfg->hal_erase_func(addr + pos);
if (rc != TSF_RES_OK)
return rc;
}
}
for (u32 pos = 0; pos < size; pos += cfg->block_size)
{
rc = tsf_init_erased_chunk(cfg, addr + pos);
if (rc != TSF_RES_OK)
return rc;
}
return TSF_RES_OK;
}
TSF_RESULT tsf_check_blank(TSF_CONFIG *cfg, u32 addr, u32 size)
{
if (!cfg || !cfg->buf || !cfg->buf_size) return TSF_RES_FS_ERROR;
while (size)
{
u16 sz = (u16)min(cfg->buf_size, size);
u8 *buf = cfg->buf;
cfg->hal_read_func(addr, buf, sz);
for (u16 j = 0; j < sz; j++)
if (*buf++ != 0xFF)
return TSF_RES_NOT_BLANK;
size -= sz;
addr += sz;
}
return TSF_RES_OK;
}
TSF_RESULT tsf_take_new_chunk(TSF_VOLUME *vol, u8 type, u32 *chunk_addr)
{
if (!vol || !vol->cfg || !chunk_addr) return TSF_RES_FS_ERROR;
if (!vol->free) return TSF_RES_BULK_FULL; // to speed up the search
TSF_CONFIG *cfg = vol->cfg;
u16 chunk_num = cfg->last_written_chunk;
u32 addr = chunk_addr(chunk_num);
TSF_CHUNK chunk;
do
{
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
if ((chunk.magic == TSF_MAGIC) && (chunk.type == TSF_CHUNK_FREE))
{
*chunk_addr = addr;
cfg->last_written_chunk = chunk_num;
vol->free -= cfg->block_size;
cfg->hal_write_func(addr + offsetof(TSF_CHUNK, type), &type, sizeof(type));
return TSF_RES_OK;
}
addr += cfg->block_size;
chunk_num++;
if (addr >= cfg->bulk_start + cfg->bulk_size)
{
addr = cfg->bulk_start;
chunk_num = 0;
}
} while (chunk_num != cfg->last_written_chunk);
return TSF_RES_BULK_FULL;
}
TSF_RESULT tsf_mount(TSF_CONFIG *cfg, TSF_VOLUME *vol)
{
if (!cfg || !vol) return TSF_RES_FS_ERROR;
if (!cfg->block_size || !cfg->bulk_size) return TSF_RES_FS_ERROR;
if ((cfg->bulk_size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
if (cfg->block_size <= sizeof(TSF_CHUNK)) return TSF_RES_FS_ERROR;
if (!cfg->hal_read_func || !cfg->hal_write_func) return TSF_RES_FS_ERROR;
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
cfg->last_written_chunk = 0;
vol->cfg = cfg;
vol->list_addr = cfg->bulk_start;
return tsf_vol_stat(vol);
}
TSF_RESULT tsf_vol_stat(TSF_VOLUME *vol)
{
if (!vol || !vol->cfg) return TSF_RES_FS_ERROR;
TSF_CONFIG *cfg = vol->cfg;
vol->free = 0;
vol->chunks_number = 0;
vol->files_number = 0;
for (u32 i = 0; i < cfg->bulk_size; i += cfg->block_size)
{
TSF_CHUNK chunk;
cfg->hal_read_func(i + cfg->bulk_start, &chunk, sizeof(TSF_CHUNK));
if (chunk.magic == TSF_MAGIC)
{
vol->chunks_number++;
if (chunk.type == TSF_CHUNK_FREE)
vol->free += cfg->block_size;
else if (chunk.type == (u8)TSF_CHUNK_HEAD)
vol->files_number++;
}
}
return TSF_RES_OK;
}
TSF_RESULT tsf_search(TSF_VOLUME *vol, u32 *chunk_addr, const char *name)
{
if (!vol || !vol->cfg || !chunk_addr || !name) return TSF_RES_FS_ERROR;
TSF_CONFIG *cfg = vol->cfg;
size_t name_len = strlen(name);
if (name_len > 255) return TSF_RES_FS_ERROR;
if (name_len && (!cfg->buf || (cfg->buf_size < name_len))) return TSF_RES_FS_ERROR;
char search_name[256];
memcpy(search_name, name, name_len);
search_name[name_len] = 0;
u8 fnlen = (u8)name_len;
for (u32 i = 0; i < cfg->bulk_size; i += cfg->block_size)
{
u32 addr = i + cfg->bulk_start;
TSF_CHUNK chunk;
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
if ((chunk.magic != TSF_MAGIC) || (chunk.type != (u8)TSF_CHUNK_HEAD))
continue;
u8 stored_fnlen;
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, fnlen), &stored_fnlen, sizeof(stored_fnlen));
if (fnlen != stored_fnlen)
continue;
if (fnlen)
{
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + sizeof(TSF_HDR), cfg->buf, fnlen);
if (memcmp(search_name, cfg->buf, fnlen))
continue;
}
*chunk_addr = addr;
return TSF_RES_OK;
}
return TSF_RES_FILE_NOT_FOUND;
}
TSF_RESULT tsf_open(TSF_VOLUME *vol, TSF_FILE *file, const char *name, u8 mode)
{
if (!vol || !file || !name) return TSF_RES_FS_ERROR;
file->mode = mode;
file->vol = vol;
if (mode == TSF_MODE_READ)
return tsf_open_for_read(file, name);
else if (mode == TSF_MODE_CREATE_WRITE)
return tsf_create(file, name);
else
return TSF_RES_MODE_ERROR;
}
TSF_RESULT tsf_open_for_read(TSF_FILE *file, const char *name)
{
TSF_VOLUME *vol = file->vol;
u32 addr;
TSF_RESULT rc = tsf_search(vol, &addr, name);
if (rc != TSF_RES_OK)
return rc;
file->addr = file->chunk_addr = addr;
vol->cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &file->size, sizeof(file->size));
file->seek = 0;
file->chunk_offset = sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + (u8)strlen(name);
vol->cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &file->next_chunk, sizeof(file->next_chunk));
return TSF_RES_OK;
}
TSF_RESULT tsf_create(TSF_FILE *file, const char *name)
{
TSF_VOLUME *vol = file->vol;
u32 addr;
size_t name_len = strlen(name);
if (name_len > 255) return TSF_RES_FS_ERROR;
TSF_CONFIG *cfg = vol->cfg;
if ((sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + name_len) > cfg->block_size)
return TSF_RES_FS_ERROR;
u8 fnlen = (u8)name_len;
#ifdef TSF_CHECK_EXIST_ON_CREATE
{
TSF_RESULT rc = tsf_search(vol, &addr, name);
if (rc != TSF_RES_FILE_NOT_FOUND)
{
if (rc == TSF_RES_OK)
return TSF_RES_FILE_EXISTS;
else
return rc;
}
}
#endif
if (tsf_take_new_chunk(vol, TSF_CHUNK_HEAD, &addr) != TSF_RES_OK)
return TSF_RES_BULK_FULL;
file->addr = addr;
file->size = 0;
file->seek = 0;
file->chunk_addr = addr;
file->chunk_offset = sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + fnlen;
file->prev_chunk_addr = addr;
file->next_chunk = 0xFFFF;
addr += sizeof(TSF_CHUNK);
TSF_HDR hdr;
hdr.size = 0xFFFFFFFF;
hdr.fnlen = fnlen;
cfg->hal_write_func(addr, &hdr, sizeof(TSF_HDR));
addr += sizeof(TSF_HDR);
cfg->hal_write_func(addr, name, fnlen);
return TSF_RES_OK;
}
TSF_RESULT tsf_close(TSF_FILE *file)
{
if ((file->mode & TSF_MODE_WRITE) == TSF_MODE_WRITE)
{
file->vol->cfg->hal_write_func(file->addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &file->size, sizeof(file->size)); // save file size
file->vol->files_number++;
}
return TSF_RES_OK;
}
TSF_RESULT tsf_read(TSF_FILE *file, void *buf, u32 size)
{
return tsf_read_int(file, buf, size);
}
TSF_RESULT tsf_seek(TSF_FILE *file, u32 size)
{
return tsf_read_int(file, 0, size);
}
TSF_RESULT tsf_read_int(TSF_FILE *file, void *buf, u32 size)
{
if (!file || !file->vol || !file->vol->cfg) return TSF_RES_FS_ERROR;
u8 *data = (u8*)buf;
TSF_CONFIG *cfg = file->vol->cfg;
u32 addr = file->chunk_addr;
if (file->seek >= file->size) return TSF_RES_OK;
size = min(size, file->size - file->seek);
while (size)
{
if (file->chunk_offset == cfg->block_size) // new chunk
{
if (file->next_chunk == 0xFFFF) return TSF_RES_FS_ERROR;
addr = file->chunk_addr = chunk_addr(file->next_chunk);
file->chunk_offset = sizeof(TSF_CHUNK);
cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &file->next_chunk, sizeof(file->next_chunk));
}
u16 sz = (u16)min(cfg->block_size - file->chunk_offset, size);
if (buf)
{
cfg->hal_read_func(file->chunk_addr + file->chunk_offset, data, sz);
data += sz;
}
file->seek += sz;
file->chunk_offset += sz;
size -= sz;
}
return TSF_RES_OK;
}
TSF_RESULT tsf_write(TSF_FILE *file, const void *buf, u32 size)
{
if (!file || !file->vol || !file->vol->cfg) return TSF_RES_FS_ERROR;
if (size && !buf) return TSF_RES_FS_ERROR;
const u8 *data = (const u8*)buf;
TSF_CONFIG *cfg = file->vol->cfg;
u32 addr = file->chunk_addr;
while (size)
{
if (file->chunk_offset == cfg->block_size) // new chunk
{
if (tsf_take_new_chunk(file->vol, TSF_CHUNK_BODY, &addr) != TSF_RES_OK)
return TSF_RES_BULK_FULL;
file->chunk_addr = addr;
file->chunk_offset = sizeof(TSF_CHUNK);
cfg->hal_write_func(file->prev_chunk_addr + offsetof(TSF_CHUNK, next_chunk), &cfg->last_written_chunk, sizeof(cfg->last_written_chunk)); // save next chunk address to the previous one, cfg->last_written_chunk is set after tsf_take_new_chunk()
}
u16 sz = (u16)min(cfg->block_size - file->chunk_offset, size);
cfg->hal_write_func(file->chunk_addr + file->chunk_offset, data, sz);
file->seek += sz;
file->size += sz;
file->chunk_offset += sz;
data += sz;
size -= sz;
if (file->chunk_offset == cfg->block_size) // close current chunk
file->prev_chunk_addr = file->chunk_addr;
}
return TSF_RES_OK;
}
TSF_RESULT tsf_delete_chunk_range(TSF_VOLUME *vol, u32 addr, u32 blocks)
{
TSF_RESULT rc;
if (!vol || !vol->cfg || !blocks) return TSF_RES_FS_ERROR;
rc = tsf_init_chunk_range(vol->cfg, addr, blocks * vol->cfg->block_size);
if (rc != TSF_RES_OK)
return rc;
vol->free += blocks * vol->cfg->block_size;
return TSF_RES_OK;
}
TSF_RESULT tsf_delete(TSF_VOLUME *vol, const char *name)
{
if (!vol || !vol->cfg || !name) return TSF_RES_FS_ERROR;
u32 addr;
u16 next;
u32 range_addr = 0;
u32 range_blocks = 0;
TSF_CONFIG *cfg = vol->cfg;
TSF_RESULT rc = tsf_search(vol, &addr, name);
if (rc != TSF_RES_OK)
return rc;
do
{
u32 cur_addr = addr;
cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &next, sizeof(next));
if (!range_blocks)
{
range_addr = cur_addr;
range_blocks = 1;
}
else if (cur_addr == range_addr + range_blocks * cfg->block_size)
{
range_blocks++;
}
else
{
rc = tsf_delete_chunk_range(vol, range_addr, range_blocks);
if (rc != TSF_RES_OK)
return rc;
range_addr = cur_addr;
range_blocks = 1;
}
if (next != 0xFFFF)
addr = chunk_addr(next);
} while (next != 0xFFFF);
rc = tsf_delete_chunk_range(vol, range_addr, range_blocks);
if (rc != TSF_RES_OK)
return rc;
if (vol->files_number)
vol->files_number--;
return TSF_RES_OK;
}
TSF_RESULT tsf_stat(TSF_VOLUME *vol, TSF_FILE_STAT *stat, const char *name)
{
if (!vol || !stat || !name) return TSF_RES_FS_ERROR;
u32 addr;
TSF_RESULT rc = tsf_search(vol, &addr, name);
if (rc != TSF_RES_OK)
return rc;
vol->cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &stat->size, sizeof(stat->size));
return TSF_RES_OK;
}
TSF_RESULT tsf_list(TSF_VOLUME *vol, u8 flag)
{
if (!vol || !vol->cfg) return TSF_RES_FS_ERROR;
TSF_CONFIG *cfg = vol->cfg;
if (!cfg->buf || !cfg->buf_size) return TSF_RES_FS_ERROR;
if (flag == TSF_LIST_START)
{
vol->list_addr = cfg->bulk_start;
return TSF_RES_OK;
}
for (u32 addr = vol->list_addr; addr < (cfg->bulk_start + cfg->bulk_size); addr += cfg->block_size)
{
TSF_CHUNK chunk;
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
if ((chunk.magic != TSF_MAGIC) || (chunk.type != (u8)TSF_CHUNK_HEAD))
continue;
u8 fnlen;
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, fnlen), &fnlen, sizeof(fnlen));
fnlen = min(fnlen, cfg->buf_size - 1);
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + sizeof(TSF_HDR), cfg->buf, fnlen);
cfg->buf[fnlen] = 0;
vol->list_addr = addr + cfg->block_size;
return TSF_RES_OK;
}
return TSF_RES_NO_MORE_FILES;
}
+177
View File
@@ -0,0 +1,177 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// #define TSF_CHECK_BLANK // to test block after erase
// #define TSF_CHECK_EXIST_ON_CREATE // to look for existing filename when file is created
typedef int64_t s64;
typedef uint64_t u64;
typedef int32_t s32;
typedef uint32_t u32;
typedef int16_t s16;
typedef uint16_t u16;
typedef int8_t s8;
typedef uint8_t u8;
#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif
#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif
// enums
enum
{
TSF_MAGIC = ((u32)0x78E15CA3)
};
typedef enum
{
TSF_RES_OK = 0,
TSF_RES_FS_ERROR,
TSF_RES_FILE_NOT_FOUND,
TSF_RES_FILE_EXISTS,
TSF_RES_MODE_ERROR,
TSF_RES_BULK_FULL,
TSF_RES_NOT_BLANK,
TSF_RES_NO_MORE_FILES
} TSF_RESULT;
typedef enum
{
TSF_CHUNK_HEAD = 0x00,
TSF_CHUNK_BODY = 0x01,
TSF_CHUNK_FREE = 0xFF
} TSF_CHUNK_TYPE;
typedef enum
{
TSF_MODE_READ = 0x01,
TSF_MODE_WRITE = 0x02,
TSF_MODE_CREATE = 0x04,
TSF_MODE_CREATE_WRITE = TSF_MODE_CREATE | TSF_MODE_WRITE,
} TSF_MODE;
typedef enum
{
TSF_LIST_NEXT = 0,
TSF_LIST_START
} TSF_LIST;
// types
typedef TSF_RESULT (*tsf_read_t)(u32, void*, u32);
typedef TSF_RESULT (*tsf_write_t)(u32, const void*, u32);
typedef TSF_RESULT (*tsf_erase_t)(u32);
typedef TSF_RESULT (*tsf_erase_range_t)(u32, u32);
#ifdef _MSC_VER
#pragma pack(push, 1)
#define TSF_PACKED
#elif defined(__GNUC__)
#define TSF_PACKED __attribute__((packed))
#else
#define TSF_PACKED
#endif
typedef struct TSF_PACKED
{
u32 magic;
u16 next_chunk;
u8 type;
} TSF_CHUNK;
typedef struct TSF_PACKED
{
u32 size;
u8 fnlen;
} TSF_HDR;
typedef struct
{
u32 bulk_start; // start address in flash IC
u32 bulk_size; // size of the TSF
u32 block_size; // erase block size
u16 last_written_chunk; // for wear levelling
tsf_read_t hal_read_func;
tsf_write_t hal_write_func;
tsf_erase_t hal_erase_func;
tsf_erase_range_t hal_erase_range_func;
u8 *buf;
u16 buf_size;
} TSF_CONFIG;
typedef struct
{
u32 free;
u16 chunks_number;
u16 files_number;
u32 list_addr;
TSF_CONFIG *cfg;
} TSF_VOLUME;
typedef struct
{
u32 addr; // first chunk address
u32 size; // file size
u32 seek; // current pointer
u32 chunk_addr; // current chunk address
u32 chunk_offset; // current offset in chunk
u32 prev_chunk_addr; // address of the previous chunk
u16 next_chunk; // next chunk
u8 mode;
TSF_VOLUME *vol;
} TSF_FILE;
typedef struct
{
u32 size;
} TSF_FILE_STAT;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
#undef TSF_PACKED
// functions
// file operations
TSF_RESULT tsf_open(TSF_VOLUME*, TSF_FILE*, const char*, u8);
TSF_RESULT tsf_read(TSF_FILE*, void*, u32);
TSF_RESULT tsf_write(TSF_FILE*, const void*, u32);
TSF_RESULT tsf_close(TSF_FILE*);
TSF_RESULT tsf_seek(TSF_FILE*, u32);
TSF_RESULT tsf_delete(TSF_VOLUME*, const char*);
TSF_RESULT tsf_stat(TSF_VOLUME*, TSF_FILE_STAT*, const char*);
TSF_RESULT tsf_list(TSF_VOLUME*, u8);
// volume operations
TSF_RESULT tsf_mount(TSF_CONFIG*, TSF_VOLUME*);
TSF_RESULT tsf_format(TSF_CONFIG*);
TSF_RESULT tsf_check(TSF_CONFIG*);
// auxilliary functions
TSF_RESULT tsf_init_chunk(TSF_CONFIG*, u32);
TSF_RESULT tsf_init_erased_chunk(TSF_CONFIG*, u32);
TSF_RESULT tsf_init_chunk_range(TSF_CONFIG*, u32, u32);
TSF_RESULT tsf_check_blank(TSF_CONFIG*, u32, u32);
TSF_RESULT tsf_search(TSF_VOLUME*, u32*, const char*);
TSF_RESULT tsf_open_for_read(TSF_FILE*, const char*);
TSF_RESULT tsf_create(TSF_FILE*, const char*);
TSF_RESULT tsf_take_new_chunk(TSF_VOLUME*, u8, u32*);
TSF_RESULT tsf_vol_stat(TSF_VOLUME*);
TSF_RESULT tsf_read_int(TSF_FILE*, void*, u32);
#define chunk_addr(chunk) (cfg->bulk_start + ((chunk) * cfg->block_size))
#ifdef __cplusplus
}
#endif
+434
View File
@@ -0,0 +1,434 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <unistd.h>
#include <math.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_chip_info.h"
#include "esp_sleep.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_private/esp_clk.h"
#include <esp_crc.h>
#include "driver/rtc_io.h"
#include "driver/uart.h"
#include "argtable3/argtable3.h"
#include "sdkconfig.h"
#include <esp_heap_caps.h>
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_intr_alloc.h"
#include "freertos/queue.h"
#include "usb/usb_host.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_mouse.h"
#include "main.h"
#include "usb_mouse.h"
#include "ps2_mouse.h"
#include "nvs_params.h"
enum usb_mouse_evt_group_t
{
USB_MOUSE_EVT_QUIT = 0,
USB_MOUSE_EVT_HID,
};
typedef struct
{
usb_mouse_evt_group_t event_group;
struct
{
hid_host_device_handle_t handle;
hid_host_driver_event_t event;
void *arg;
} hid;
} usb_mouse_evt_t;
QueueHandle_t usb_mouse_evt_queue = NULL;
TaskHandle_t usb_mouse_task_handle = NULL;
TaskHandle_t usb_mouse_lib_task_handle = NULL;
hid_host_device_handle_t usb_mouse_dev = NULL;
volatile bool usb_mouse_mode_active = false;
int usb_mouse_abs_x = 0;
int usb_mouse_abs_y = 0;
void usb_mouse_report_callback(const uint8_t *data, int length)
{
if (length < (int)sizeof(hid_mouse_input_report_boot_t))
return;
const hid_mouse_input_report_boot_t *r = (const hid_mouse_input_report_boot_t *)data;
usb_mouse_abs_x += r->x_displacement;
usb_mouse_abs_y += r->y_displacement;
ps2_mouse_send_movement((int)r->x_displacement,
-(int)r->y_displacement,
(unsigned)r->buttons.val);
}
void usb_mouse_interface_callback(hid_host_device_handle_t hid_device_handle, hid_host_interface_event_t event, void *arg)
{
(void)arg;
uint8_t data[64];
size_t data_length = 0;
hid_host_dev_params_t dev_params;
memset(data, 0, sizeof(data));
if (hid_host_device_get_params(hid_device_handle, &dev_params) != ESP_OK)
return;
switch (event)
{
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
if (hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length) != ESP_OK)
return;
if (dev_params.sub_class == HID_SUBCLASS_BOOT_INTERFACE && dev_params.proto == HID_PROTOCOL_MOUSE)
usb_mouse_report_callback(data, (int)data_length);
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
printf("usb mouse disconnected\r\n");
if (usb_mouse_dev == hid_device_handle)
usb_mouse_dev = NULL;
usb_mouse_abs_x = 0;
usb_mouse_abs_y = 0;
hid_host_device_close(hid_device_handle);
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
printf("usb mouse transfer error\r\n");
break;
default:
break;
}
}
void usb_mouse_process_device_event(hid_host_device_handle_t hid_device_handle, hid_host_driver_event_t event, void *arg)
{
(void)arg;
hid_host_dev_params_t dev_params;
if (hid_host_device_get_params(hid_device_handle, &dev_params) != ESP_OK)
return;
if (event != HID_HOST_DRIVER_EVENT_CONNECTED)
return;
if (dev_params.proto != HID_PROTOCOL_MOUSE)
{
printf("usb hid connected, but not a mouse (proto=%d)\r\n", (int)dev_params.proto);
return;
}
if (dev_params.sub_class != HID_SUBCLASS_BOOT_INTERFACE)
{
printf("usb mouse connected, but not boot protocol\r\n");
return;
}
const hid_host_device_config_t dev_config =
{
.callback = usb_mouse_interface_callback,
.callback_arg = NULL,
};
if (hid_host_device_open(hid_device_handle, &dev_config) != ESP_OK)
{
printf("hid_host_device_open failed\r\n");
return;
}
if (hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) != ESP_OK)
{
printf("hid_class_request_set_protocol failed\r\n");
hid_host_device_close(hid_device_handle);
return;
}
if (hid_host_device_start(hid_device_handle) != ESP_OK)
{
printf("hid_host_device_start failed\r\n");
hid_host_device_close(hid_device_handle);
return;
}
usb_mouse_dev = hid_device_handle;
usb_mouse_abs_x = 0;
usb_mouse_abs_y = 0;
printf("usb mouse connected\r\n");
}
void usb_mouse_device_callback(hid_host_device_handle_t hid_device_handle, hid_host_driver_event_t event, void *arg)
{
usb_mouse_evt_t evt = {};
evt.event_group = USB_MOUSE_EVT_HID;
evt.hid.handle = hid_device_handle;
evt.hid.event = event;
evt.hid.arg = arg;
if (usb_mouse_evt_queue)
xQueueSend(usb_mouse_evt_queue, &evt, 0);
}
void usb_mouse_lib_task(void *arg)
{
const usb_host_config_t host_config =
{
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LOWMED,
};
esp_err_t err = usb_host_install(&host_config);
if (err != ESP_OK)
{
printf("usb_host_install failed: %s\r\n", esp_err_to_name(err));
usb_mouse_lib_task_handle = NULL;
xTaskNotifyGive((TaskHandle_t)arg);
vTaskDelete(NULL);
return;
}
xTaskNotifyGive((TaskHandle_t)arg);
while (1)
{
uint32_t event_flags = 0;
err = usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
if (err != ESP_OK)
{
printf("usb_host_lib_handle_events failed: %s\r\n", esp_err_to_name(err));
break;
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
{
usb_host_device_free_all();
break;
}
}
vTaskDelay(pdMS_TO_TICKS(10));
usb_host_uninstall();
usb_mouse_lib_task_handle = NULL;
vTaskDelete(NULL);
}
void usb_mouse_task(void *arg)
{
(void)arg;
usb_mouse_evt_t evt;
esp_err_t err;
usb_mouse_evt_queue = xQueueCreateWithCaps(8, sizeof(usb_mouse_evt_t), task_ram_type_non_critical);
if (!usb_mouse_evt_queue)
{
printf("usb mouse queue create failed\r\n");
usb_mouse_mode_active = false;
usb_mouse_task_handle = NULL;
vTaskDelete(NULL);
return;
}
if (xTaskCreatePinnedToCoreWithCaps(usb_mouse_lib_task, "usb_mouse_lib", 2048, xTaskGetCurrentTaskHandle(), USB_MOUSE_LIB_TASK_PRIO, &usb_mouse_lib_task_handle, 0, task_ram_type_critical) != pdTRUE)
{
printf("usb mouse lib task create failed\r\n");
vQueueDeleteWithCaps(usb_mouse_evt_queue);
usb_mouse_evt_queue = NULL;
usb_mouse_mode_active = false;
usb_mouse_task_handle = NULL;
vTaskDelete(NULL);
return;
}
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000));
const hid_host_driver_config_t hid_cfg =
{
.create_background_task = true,
.task_priority = USB_MOUSE_HID_TASK_PRIO,
.stack_size = 4096,
.core_id = 0,
.callback = usb_mouse_device_callback,
.callback_arg = NULL,
};
err = hid_host_install(&hid_cfg);
if (err != ESP_OK)
{
printf("hid_host_install failed: %s\r\n", esp_err_to_name(err));
vQueueDeleteWithCaps(usb_mouse_evt_queue);
usb_mouse_evt_queue = NULL;
usb_mouse_mode_active = false;
usb_mouse_task_handle = NULL;
vTaskDelete(NULL);
return;
}
printf("USB switched to host mode, connect mouse\r\n");
while (1)
{
if (!xQueueReceive(usb_mouse_evt_queue, &evt, portMAX_DELAY))
continue;
if (evt.event_group == USB_MOUSE_EVT_QUIT)
break;
if (evt.event_group == USB_MOUSE_EVT_HID)
usb_mouse_process_device_event(evt.hid.handle, evt.hid.event, evt.hid.arg);
}
if (usb_mouse_dev)
{
hid_host_device_stop(usb_mouse_dev);
hid_host_device_close(usb_mouse_dev);
usb_mouse_dev = NULL;
}
hid_host_uninstall();
for (int i = 0; i < 100; i++)
{
if (usb_mouse_lib_task_handle == NULL)
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
QueueHandle_t q = usb_mouse_evt_queue;
usb_mouse_evt_queue = NULL;
if (q) vQueueDeleteWithCaps(q);
usb_mouse_mode_active = false;
usb_mouse_task_handle = NULL;
vTaskDelete(NULL);
}
extern "C" esp_err_t usb_mouse_start()
{
if (usb_mouse_mode_active)
return ESP_ERR_INVALID_STATE;
usb_mouse_mode_active = true;
if (xTaskCreatePinnedToCoreWithCaps(usb_mouse_task, "usb_mouse", 4096, NULL, USB_MOUSE_TASK_PRIO, &usb_mouse_task_handle, 0, task_ram_type_non_critical) != pdTRUE)
{
usb_mouse_mode_active = false;
usb_mouse_task_handle = NULL;
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
int usb_cmd(int argc, char **argv)
{
if (argc < 2)
{
printf("Usage:\r\n");
printf(" usb en <0|1>\r\n");
printf(" usb up\r\n");
return 0;
}
if (!strcmp(argv[1], "up"))
{
if (argc != 2)
{
printf("Usage: usb up\r\n");
return 1;
}
esp_err_t err = usb_mouse_start();
if (err == ESP_ERR_INVALID_STATE)
{
printf("usb mouse mode already active\r\n");
return 0;
}
if (err != ESP_OK)
{
printf("usb mouse task create failed: %s\r\n", esp_err_to_name(err));
return 1;
}
printf("usb mouse host started until reset (NVS unchanged)\r\n");
return 0;
}
if (strcmp(argv[1], "en"))
{
printf("Unknown subcommand: %s\r\n", argv[1]);
printf("Usage:\r\n");
printf(" usb en <0|1>\r\n");
printf(" usb up\r\n");
return 1;
}
if (argc < 3)
{
printf("Usage: usb en <0|1>\r\n");
return 1;
}
char *endp = NULL;
unsigned long en = strtoul(argv[2], &endp, 0);
if (!endp || *endp || en > 1)
{
printf("Bad <0|1>: %s\r\n", argv[2]);
return 1;
}
app_params.usb_mode = (uint8_t)en;
esp_err_t err = app_params_save();
if (err != ESP_OK)
{
printf("app_params_save failed: %s\r\n", esp_err_to_name(err));
return 1;
}
printf("USB enable: %u\r\n", app_params.usb_mode);
if (app_params.usb_mode)
{
err = usb_mouse_start();
if (err == ESP_ERR_INVALID_STATE)
{
printf("usb mouse mode already active\r\n");
return 0;
}
if (err != ESP_OK)
{
printf("usb mouse task create failed: %s\r\n", esp_err_to_name(err));
return 1;
}
printf("usb mouse host started\r\n");
return 0;
}
if (usb_mouse_mode_active)
printf("USB disable saved, active host mode will stay until reset\r\n");
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "esp_err.h"
extern "C" esp_err_t usb_mouse_start();
int usb_cmd(int argc, char **argv);
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
#pragma once
// Phase 1 of the SMB2/3 network-share feature (see project plan): an SMB2/3
// server task with a hardcoded fake filesystem, to prove the protocol layer
// works before anything is wired to the Z80/Wild Commander side.
#define VFS_SHARE_NAME_MAX 32
extern char vfs_share_name[VFS_SHARE_NAME_MAX + 1];
void vfs_bridge_init();
void smb_server_task(void *arg);
// Start/stop the SMB2/3 server on demand (ESP_CMD_VFS_SERVER_START/STOP,
// dispatched via TASK_VFS_SERVER_START/STOP in helper_task) instead of it
// running unconditionally once WiFi comes up - the Z80 side is single-
// tasking (it can only ever be driving one plugin at a time), so there is
// no point keeping the SMB server (or the other services it pauses -
// at_uart_suspend()/tracker_suspend()) active except while the smbvfs.wmf
// plugin is actually the thing running.
void vfs_bridge_start_server();
void vfs_bridge_stop_server();
+124
View File
@@ -0,0 +1,124 @@
// See vfs_request.h. Bridges vfs_bridge.cpp's SMB2 filesystem callbacks
// (running on smb_server_task) to the Z80 side over the SPI-slave
// protocol. Shared state is protected by a portMUX spinlock rather than a
// FreeRTOS mutex because vfs_spi_poll()/vfs_spi_get_request_data() run in
// spi_slave.cpp's IRAM_ATTR ISR-context command dispatch (mirrors
// ps2_mouse.cpp's ps2_mouse_lock pattern already used in this codebase).
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "main.h"
#include "spi_slave.h"
#include "vfs_request.h"
static portMUX_TYPE vfs_lock = portMUX_INITIALIZER_UNLOCKED;
static SemaphoreHandle_t vfs_call_mtx = NULL; // serializes concurrent vfs_command_sync() callers
static QueueHandle_t vfs_ack_queue = NULL; // depth 1, posted to by vfs_spi_submit_result()
static uint8_t s_req_buf[VFS_REQUEST_MAX];
static uint32_t s_req_len = 0;
static uint8_t s_req_op = 0;
static bool s_req_pending = false;
static uint8_t s_result_buf[DMA_BUF_SIZE];
static uint32_t s_result_len = 0;
static uint8_t s_result_status = 0;
void vfs_request_init()
{
if (!vfs_call_mtx) vfs_call_mtx = xSemaphoreCreateMutex();
if (!vfs_ack_queue) vfs_ack_queue = xQueueCreate(1, sizeof(uint8_t));
}
bool vfs_command_sync(uint8_t op, const uint8_t *req_payload, uint32_t req_len,
uint32_t timeout_ms, uint8_t *out_status,
const uint8_t **out_data, uint32_t *out_len)
{
if (req_len > VFS_REQUEST_MAX) return false;
ESP_LOGI("vfs_req", "sync: op=%u waiting for mutex", (unsigned)op);
xSemaphoreTake(vfs_call_mtx, portMAX_DELAY);
ESP_LOGI("vfs_req", "sync: op=%u got mutex, queuing request", (unsigned)op);
xQueueReset(vfs_ack_queue); // drop any stale ack from a previous timed-out call
portENTER_CRITICAL(&vfs_lock);
memcpy(s_req_buf, req_payload, req_len);
s_req_len = req_len;
s_req_op = op;
s_req_pending = true;
portEXIT_CRITICAL(&vfs_lock);
uint8_t dummy;
bool got_answer = xQueueReceive(vfs_ack_queue, &dummy, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
ESP_LOGI("vfs_req", "sync: op=%u %s", (unsigned)op, got_answer ? "got answer" : "TIMED OUT");
if (!got_answer)
{
// Give up waiting. A late answer from the Z80 side after this point
// would otherwise land on whatever the NEXT call queues up - clearing
// req_pending here means a late GET_REQUEST_DATA/SUBMIT_RESULT for
// THIS request just sees nothing pending / is harmlessly dropped.
portENTER_CRITICAL(&vfs_lock);
s_req_pending = false;
portEXIT_CRITICAL(&vfs_lock);
xSemaphoreGive(vfs_call_mtx);
return false;
}
portENTER_CRITICAL(&vfs_lock);
*out_status = s_result_status;
*out_data = s_result_buf;
*out_len = s_result_len;
portEXIT_CRITICAL(&vfs_lock);
xSemaphoreGive(vfs_call_mtx);
return true;
}
bool IRAM_ATTR vfs_spi_poll(uint8_t *out_op, uint32_t *out_size)
{
portENTER_CRITICAL_ISR(&vfs_lock);
bool pending = s_req_pending;
if (pending)
{
*out_op = s_req_op;
*out_size = s_req_len;
}
portEXIT_CRITICAL_ISR(&vfs_lock);
return pending;
}
uint32_t IRAM_ATTR vfs_spi_get_request_data(uint8_t *dma_buf, uint32_t max_size)
{
portENTER_CRITICAL_ISR(&vfs_lock);
uint32_t n = s_req_len;
if (n > max_size) n = max_size;
if (s_req_pending) memcpy(dma_buf, s_req_buf, n);
else n = 0;
portEXIT_CRITICAL_ISR(&vfs_lock);
return n;
}
void IRAM_ATTR vfs_spi_submit_result(uint8_t status, const uint8_t *dma_buf, uint32_t size)
{
if (size > DMA_BUF_SIZE) size = DMA_BUF_SIZE;
portENTER_CRITICAL_ISR(&vfs_lock);
memcpy(s_result_buf, dma_buf, size);
s_result_len = size;
s_result_status = status;
s_req_pending = false;
portEXIT_CRITICAL_ISR(&vfs_lock);
uint8_t dummy = 1;
xQueueSendFromISR(vfs_ack_queue, &dummy, NULL);
portYIELD_FROM_ISR();
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
// SPI-side half of the VFS bridge (see project plan, "Phase 2"). Turns an
// SMB2 filesystem callback (from vfs_bridge.cpp) into one request/response
// round trip over the existing SPI-slave protocol, answered by whatever is
// polling ESP_CMD_VFS_POLL on the Z80 side - a hardcoded test responder for
// now (test_spi_esp32), the real Wild Commander plugin once vfs.asm is
// ported (Phase 3).
//
// Wire format (all multi-byte fields little-endian; this is our own
// convention for now, not yet matched to vfs.asm's - revisit when porting
// it in Phase 3):
//
// Request (from ESP_CMD_VFS_GET_REQUEST_DATA), path-based ops
// (STAT/OPEN/DELETE/MKDIR/RMDIR/READDIR):
// u8 op (VFS_OP_*)
// u16 path_len
// u8 path[path_len] (UTF-8, no NUL terminator)
//
// Request, handle-based ops:
// READ: u8 op, u32 handle, u64 offset, u32 size
// WRITE: u8 op, u32 handle, u64 offset, u32 size, u8 data[size]
// CLOSE: u8 op, u32 handle
//
// Result (to ESP_CMD_VFS_SUBMIT_RESULT): status travels in ESP_REG_VFS_STATUS
// (0 = success), separate from the DMA payload below, which on error is empty:
// STAT: u8 is_dir, u64 size
// READDIR: u16 count, then count * { u8 name_len, u8 name[name_len], u8 is_dir, u64 size }
// OPEN: u32 handle
// READ: u8 data[N] (N inferred from the transfer size)
// WRITE: u32 written
// CLOSE/DELETE/MKDIR/RMDIR: (empty)
#include <stdint.h>
#include <stddef.h>
#define VFS_REQUEST_MAX 512 // path-based requests are small; enough headroom
// Result buffer is sized to DMA_BUF_SIZE (spi_slave.h, 16384) inside
// vfs_request.cpp - not redeclared here to avoid a second source of truth.
void vfs_request_init();
// Called from the SMB2 server task (vfs_bridge.cpp) for every filesystem
// operation. Builds the request payload, blocks until the Z80 side answers
// via ESP_CMD_VFS_SUBMIT_RESULT (or times out), and returns the raw result
// bytes. Returns false on timeout (no answer within timeout_ms); *out_data
// is only valid until the next call to vfs_command_sync (single shared
// buffer, single in-flight request at a time - matches this codebase's
// existing single-session philosophy).
bool vfs_command_sync(uint8_t op, const uint8_t *req_payload, uint32_t req_len,
uint32_t timeout_ms, uint8_t *out_status,
const uint8_t **out_data, uint32_t *out_len);
// ---- Called from spi_slave.cpp's ISR dispatch / DREQ handlers -----------
// ESP_CMD_VFS_POLL: cheap, answered directly in the ISR.
bool vfs_spi_poll(uint8_t *out_op, uint32_t *out_size);
// DREQ_VFS_REQUEST (prepare_tx_data): copies the pending request's payload
// into dma_buf. Returns the number of bytes copied.
uint32_t vfs_spi_get_request_data(uint8_t *dma_buf, uint32_t max_size);
// DREQ_VFS_RESULT (process_rx_data): copies dma_buf into the result buffer
// and wakes up the waiting vfs_command_sync() call.
void vfs_spi_submit_result(uint8_t status, const uint8_t *dma_buf, uint32_t size);
+243
View File
@@ -0,0 +1,243 @@
// WEATHER_GET adapted from ZiFi-ESP32-S3-Zero (616bee6); wire parser retained.
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstddef>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_http_client.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "nvs.h"
#include "main.h"
#include "esp_spi_defs.h"
#include "spi_slave.h"
#include "mem_obj.h"
#include "wifi.h"
#include "weather.h"
#include "zifi/weather_parse.hpp"
namespace {
static const char *TAG = "weather";
constexpr size_t body_capacity = 4096;
char cached_country[3] = {};
char cached_zip[32] = {};
zifi::GeoResult cached_geo = {};
bool have_geo = false;
constexpr uint32_t geo_cache_magic = 0x314f4547; // "GEO1"
struct PersistedGeo {
uint32_t magic;
char country[3];
char zip[32];
zifi::GeoResult geo;
uint32_t checksum;
};
uint32_t geo_checksum(const PersistedGeo &value)
{
const auto *bytes = reinterpret_cast<const uint8_t *>(&value);
uint32_t hash = 2166136261u;
for (size_t i = 0; i < offsetof(PersistedGeo, checksum); ++i) {
hash = (hash ^ bytes[i]) * 16777619u;
}
return hash;
}
bool load_geo_cache(const char *country, const char *zip)
{
nvs_handle_t handle = 0;
if (nvs_open("weather", NVS_READONLY, &handle) != ESP_OK) return false;
PersistedGeo saved = {};
size_t size = sizeof(saved);
esp_err_t result = nvs_get_blob(handle, "geo", &saved, &size);
nvs_close(handle);
if (result != ESP_OK || size != sizeof(saved) || saved.magic != geo_cache_magic ||
saved.checksum != geo_checksum(saved) ||
saved.country[sizeof(saved.country) - 1] != 0 ||
saved.zip[sizeof(saved.zip) - 1] != 0 ||
saved.geo.place[sizeof(saved.geo.place) - 1] != 0 ||
strcmp(saved.country, country) || strcmp(saved.zip, zip) ||
!std::isfinite(saved.geo.latitude) || !std::isfinite(saved.geo.longitude) ||
saved.geo.latitude < -90.0f || saved.geo.latitude > 90.0f ||
saved.geo.longitude < -180.0f || saved.geo.longitude > 180.0f) return false;
cached_geo = saved.geo;
strcpy(cached_country, saved.country);
strcpy(cached_zip, saved.zip);
have_geo = true;
ESP_LOGI(TAG, "using saved coordinates for %s/%s", country, zip);
return true;
}
void save_geo_cache(const char *country, const char *zip)
{
PersistedGeo saved = {};
saved.magic = geo_cache_magic;
strlcpy(saved.country, country, sizeof(saved.country));
strlcpy(saved.zip, zip, sizeof(saved.zip));
saved.geo = cached_geo;
saved.checksum = geo_checksum(saved);
nvs_handle_t handle = 0;
esp_err_t result = nvs_open("weather", NVS_READWRITE, &handle);
if (result == ESP_OK) {
result = nvs_set_blob(handle, "geo", &saved, sizeof(saved));
if (result == ESP_OK) result = nvs_commit(handle);
nvs_close(handle);
}
if (result != ESP_OK) ESP_LOGW(TAG, "cannot save coordinates: %s", esp_err_to_name(result));
}
// Each attempt has a 15s I/O timeout and body deadline. New attempts may
// only start during the first 30s of the command, including after backoff.
bool download(const char *url, char *body, size_t &length, int64_t deadline,
char *error, size_t error_size)
{
for (int attempt = 0; attempt < 3; ++attempt) {
if (esp_timer_get_time() >= deadline) {
snprintf(error, error_size, "timeout");
return false;
}
esp_http_client_config_t cfg = {};
cfg.url = url;
cfg.timeout_ms = 15000;
cfg.disable_auto_redirect = true;
cfg.user_agent = "ZX-Evo-weather/1";
auto client = esp_http_client_init(&cfg);
if (!client) {
snprintf(error, error_size, "no memory");
return false;
}
length = 0;
bool ok = false, retry = true;
ESP_LOGI(TAG, "GET attempt %d: %s", attempt + 1, url);
snprintf(error, error_size, "connection failed");
esp_err_t open_result = esp_http_client_open(client, 0);
if (open_result == ESP_OK && esp_http_client_fetch_headers(client) >= 0) {
int status = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP %d from %s", status, url);
if (status < 200 || status >= 300) {
snprintf(error, error_size, "http %d", status);
retry = status == 429 || status >= 500;
} else if (esp_http_client_get_content_length(client) >= body_capacity) {
snprintf(error, error_size, "reply too long");
retry = false;
} else {
const int64_t body_deadline = esp_timer_get_time() + 15000000;
snprintf(error, error_size, "incomplete reply");
while (esp_timer_get_time() < body_deadline) {
int n = esp_http_client_read(client, body + length, body_capacity - 1 - length);
if (n > 0) length += n;
if (esp_http_client_is_complete_data_received(client)) {
ok = length != 0;
break;
}
if (length == body_capacity - 1) {
snprintf(error, error_size, "reply too long");
retry = false;
break;
}
if (n == -ESP_ERR_HTTP_EAGAIN) { vTaskDelay(1); continue; }
if (n <= 0) break;
}
}
} else if (open_result != ESP_OK) {
snprintf(error, error_size, "connect: %s", esp_err_to_name(open_result));
ESP_LOGW(TAG, "open failed for %s: %s", url, esp_err_to_name(open_result));
} else {
snprintf(error, error_size, "header failed");
ESP_LOGW(TAG, "headers failed for %s", url);
}
esp_http_client_cleanup(client);
body[length] = 0;
if (ok) return true;
if (!retry || attempt == 2) return false;
ESP_LOGW(TAG, "retrying after: %s", error);
vTaskDelay(pdMS_TO_TICKS(1000 * (attempt + 1)));
}
return false;
}
}
void weather_do_get()
{
const int input = rd_reg8(ESP_REG_OBJ_HANDLE);
wr_reg32(ESP_REG_DATA_SIZE, 0);
if (!check_handle(input) ||
(mem_obj[input].type != OBJ_TYPE_DATA && mem_obj[input].type != OBJ_TYPE_DATAF)) {
set_status(ESP_ERR_INV_HANDLE);
return;
}
// Request: two NUL-terminated strings, ISO country code and postal code.
const char *data = static_cast<const char *>(mem_obj[input].addr);
const size_t size = mem_obj[input].size;
if (size < 5 || !data || data[2] != 0 ||
!((data[0] >= 'A' && data[0] <= 'Z') || (data[0] >= 'a' && data[0] <= 'z')) ||
!((data[1] >= 'A' && data[1] <= 'Z') || (data[1] >= 'a' && data[1] <= 'z'))) {
set_status(ESP_ERR_INV_PARAM);
return;
}
size_t zip_len = strnlen(data + 3, size - 3 < sizeof(cached_zip) ? size - 3 : sizeof(cached_zip));
if (!zip_len || zip_len >= sizeof(cached_zip) || zip_len >= size - 3) {
set_status(ESP_ERR_INV_STR_LEN);
return;
}
char country[3] = {data[0], data[1], 0};
char zip[32];
memcpy(zip, data + 3, zip_len + 1);
if (!wifi_is_connected()) { set_status(ESP_ERR_AP_NOT_CONNECTED); return; }
char *body = static_cast<char *>(malloc(body_capacity));
if (!body) { set_status(ESP_ERR_OUT_OF_MEMORY); return; }
const int64_t deadline = esp_timer_get_time() + 30000000;
char url[512], error[64] = {};
size_t length = 0;
bool ok = true;
if (!have_geo || strcmp(country, cached_country) || strcmp(zip, cached_zip)) {
have_geo = load_geo_cache(country, zip);
}
if (!have_geo) {
char encoded[94];
size_t j = 0;
for (size_t i = 0; i < zip_len; ++i) {
unsigned char c = zip[i];
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '-') encoded[j++] = c;
else { snprintf(encoded + j, sizeof(encoded) - j, "%%%02X", c); j += 3; }
}
encoded[j] = 0;
snprintf(url, sizeof(url), "http://api.zippopotam.us/%s/%s", country, encoded);
ok = download(url, body, length, deadline, error, sizeof(error)) &&
zifi::parseZippopotam(body, length, cached_geo, error, sizeof(error));
if (ok) {
strcpy(cached_country, country);
strcpy(cached_zip, zip);
have_geo = true;
save_geo_cache(country, zip);
}
}
uint8_t record[zifi::kWeatherRecordSize] = {};
if (ok) {
snprintf(url, sizeof(url),
"http://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f"
"&current=temperature_2m,weather_code,is_day,wind_speed_10m,precipitation,surface_pressure"
"&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,sunrise,sunset"
"&timezone=auto&forecast_days=6&timeformat=unixtime",
static_cast<double>(cached_geo.latitude), static_cast<double>(cached_geo.longitude));
ok = download(url, body, length, deadline, error, sizeof(error)) &&
zifi::parseOpenMeteo(body, length, cached_geo.place, record, error, sizeof(error));
}
free(body);
if (!ok) {
ESP_LOGW(TAG, "%s", error);
set_status(ESP_ERR_WEATHER);
return;
}
int output = make_obj(sizeof(record), OBJ_TYPE_DATA);
if (output < 0) return; // make_obj publishes the precise allocation error.
memcpy(mem_obj[output].addr, record, sizeof(record));
wr_reg8(ESP_REG_OBJ_HANDLE, output);
wr_reg32(ESP_REG_DATA_OFFSET, 0);
wr_reg32(ESP_REG_DATA_SIZE, sizeof(record));
set_status(ESP_ST_READY);
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
// Called only by the serialized helper task.
void weather_do_get();
+723
View File
@@ -0,0 +1,723 @@
#include "zifi/weather_parse.hpp"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
namespace zifi {
namespace {
// --- крошечный разбор JSON: ровно то, что нужно двум ответам -----------------------
// Полного дерева не строим: ищем ключи на верхнем уровне объекта, пропуская
// чужие значения целиком. Этого достаточно, потому что структура ответов
// известна заранее, а память ESP дорога.
void setError(char* error, size_t errorSize, const char* text) {
if (error != nullptr && errorSize != 0) {
snprintf(error, errorSize, "%s", text);
}
}
bool isSpace(char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
const char* skipSpaces(const char* p, const char* end) {
while (p < end && isSpace(*p)) {
++p;
}
return p;
}
// p указывает на открывающую кавычку; вернуть позицию за закрывающей.
const char* skipString(const char* p, const char* end) {
++p;
while (p < end) {
if (*p == '\\') {
p += 2;
continue;
}
if (*p == '"') {
return p + 1;
}
++p;
}
return nullptr;
}
// Пропустить любое значение: объект, массив, строку, число или литерал.
const char* skipValue(const char* p, const char* end) {
p = skipSpaces(p, end);
if (p >= end) {
return nullptr;
}
if (*p == '"') {
return skipString(p, end);
}
if (*p == '{' || *p == '[') {
int depth = 0;
while (p < end) {
if (*p == '"') {
p = skipString(p, end);
if (p == nullptr) {
return nullptr;
}
continue;
}
if (*p == '{' || *p == '[') {
++depth;
} else if (*p == '}' || *p == ']') {
--depth;
if (depth == 0) {
return p + 1;
}
}
++p;
}
return nullptr;
}
while (p < end && *p != ',' && *p != '}' && *p != ']' && !isSpace(*p)) {
++p;
}
return p;
}
// Найти ключ на верхнем уровне объекта [begin, end) и вернуть указатель на
// его значение; nullptr — ключа нет.
const char* findKey(const char* begin, const char* end, const char* key) {
const size_t keyLength = strlen(key);
const char* p = skipSpaces(begin, end);
if (p < end && *p == '{') {
++p;
}
while (true) {
p = skipSpaces(p, end);
if (p >= end || *p != '"') {
return nullptr;
}
const char* nameEnd = skipString(p, end);
if (nameEnd == nullptr) {
return nullptr;
}
const bool match = static_cast<size_t>(nameEnd - p) == keyLength + 2 &&
memcmp(p + 1, key, keyLength) == 0;
p = skipSpaces(nameEnd, end);
if (p >= end || *p != ':') {
return nullptr;
}
p = skipSpaces(p + 1, end);
if (match) {
return p;
}
p = skipValue(p, end);
if (p == nullptr) {
return nullptr;
}
p = skipSpaces(p, end);
if (p < end && *p == ',') {
++p;
}
}
}
// Число (возможно в кавычках, как lat/lon у Nominatim); null считается нулём.
bool parseNumber(const char* p, const char* end, double& out, const char** next) {
p = skipSpaces(p, end);
if (p >= end) {
return false;
}
bool quoted = false;
if (*p == '"') {
quoted = true;
++p;
}
if (end - p >= 4 && memcmp(p, "null", 4) == 0) {
out = 0.0;
p += 4;
} else {
char* stop = nullptr;
out = strtod(p, &stop);
if (stop == p || stop > end) {
return false;
}
p = stop;
}
if (quoted) {
if (p >= end || *p != '"') {
return false;
}
++p;
}
if (next != nullptr) {
*next = p;
}
return true;
}
void appendUtf8(char* out, size_t capacity, size_t& length, uint32_t code) {
char buffer[4];
size_t count;
if (code < 0x80) {
buffer[0] = static_cast<char>(code);
count = 1;
} else if (code < 0x800) {
buffer[0] = static_cast<char>(0xC0 | (code >> 6));
buffer[1] = static_cast<char>(0x80 | (code & 0x3F));
count = 2;
} else {
buffer[0] = static_cast<char>(0xE0 | (code >> 12));
buffer[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
buffer[2] = static_cast<char>(0x80 | (code & 0x3F));
count = 3;
}
for (size_t i = 0; i < count && length + 1 < capacity; ++i) {
out[length++] = buffer[i];
}
}
// Строка в кавычках с раскодированием \", \\, \/ и \uXXXX (в UTF-8).
bool parseString(const char* p, const char* end, char* out, size_t capacity) {
p = skipSpaces(p, end);
if (p >= end || *p != '"' || capacity == 0) {
return false;
}
++p;
size_t length = 0;
while (p < end && *p != '"') {
char c = *p++;
if (c == '\\' && p < end) {
c = *p++;
if (c == 'u' && end - p >= 4) {
char hex[5] = {p[0], p[1], p[2], p[3], 0};
appendUtf8(out, capacity, length, static_cast<uint32_t>(strtoul(hex, nullptr, 16)));
p += 4;
continue;
}
if (c == 'n' || c == 't' || c == 'r') {
c = ' ';
}
}
if (length + 1 < capacity) {
out[length++] = c;
}
}
out[length] = 0;
return p < end;
}
// Массив чисел -> out[0..max); вернуть число прочитанных элементов.
size_t parseNumberArray(const char* p, const char* end, double* out, size_t max) {
p = skipSpaces(p, end);
if (p >= end || *p != '[') {
return 0;
}
++p;
size_t count = 0;
while (true) {
p = skipSpaces(p, end);
if (p >= end || *p == ']') {
return count;
}
double value = 0.0;
const char* next = nullptr;
if (!parseNumber(p, end, value, &next)) {
return count;
}
if (count < max) {
out[count] = value;
}
++count;
p = skipSpaces(next, end);
if (p < end && *p == ',') {
++p;
}
}
}
bool objectNumber(const char* begin, const char* end, const char* key, double& out) {
const char* value = findKey(begin, end, key);
return value != nullptr && parseNumber(value, end, out, nullptr);
}
// --- календарь для unixtime -----------------------------------------------------------
// Дата по числу дней от 1970-01-01 (алгоритм Говарда Хиннанта).
void civilFromDays(int64_t z, int& year, unsigned& month, unsigned& day) {
z += 719468;
const int64_t era = (z >= 0 ? z : z - 146096) / 146097;
const unsigned doe = static_cast<unsigned>(z - era * 146097);
const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
const int64_t y = static_cast<int64_t>(yoe) + era * 400;
const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
const unsigned mp = (5 * doy + 2) / 153;
day = doy - (153 * mp + 2) / 5 + 1;
month = mp < 10 ? mp + 3 : mp - 9;
year = static_cast<int>(y + (month <= 2 ? 1 : 0));
}
struct LocalTime {
int year;
unsigned month;
unsigned day;
unsigned weekday; // 0 — понедельник
unsigned hour;
unsigned minute;
};
LocalTime localTime(int64_t unixSeconds, int32_t offsetSeconds) {
const int64_t local = unixSeconds + offsetSeconds;
int64_t days = local / 86400;
int64_t seconds = local - days * 86400;
if (seconds < 0) {
seconds += 86400;
--days;
}
LocalTime result;
civilFromDays(days, result.year, result.month, result.day);
// 1970-01-01 — четверг, то есть 3 при нумерации с понедельника.
result.weekday = static_cast<unsigned>(((days + 3) % 7 + 7) % 7);
result.hour = static_cast<unsigned>(seconds / 3600);
result.minute = static_cast<unsigned>((seconds % 3600) / 60);
return result;
}
uint8_t clampU8(double value) {
const long rounded = lround(value);
return static_cast<uint8_t>(rounded < 0 ? 0 : (rounded > 255 ? 255 : rounded));
}
int8_t clampI8(double value) {
const long rounded = lround(value);
return static_cast<int8_t>(rounded < -128 ? -128 : (rounded > 127 ? 127 : rounded));
}
uint16_t clampU16(double value) {
const long rounded = lround(value);
return static_cast<uint16_t>(rounded < 0 ? 0 : (rounded > 65535 ? 65535 : rounded));
}
void putU16(uint8_t* at, uint16_t value) {
at[0] = static_cast<uint8_t>(value);
at[1] = static_cast<uint8_t>(value >> 8);
}
// Латиница с диакритикой (U+00C0..U+017F) -> базовая буква ASCII.
char latinBase(uint32_t code) {
static const struct {
uint32_t first;
uint32_t last;
char base;
} kRanges[] = {
{0xC0, 0xC5, 'A'}, {0xC7, 0xC7, 'C'}, {0xC8, 0xCB, 'E'}, {0xCC, 0xCF, 'I'},
{0xD1, 0xD1, 'N'}, {0xD2, 0xD6, 'O'}, {0xD8, 0xD8, 'O'}, {0xD9, 0xDC, 'U'},
{0xDD, 0xDD, 'Y'}, {0xDF, 0xDF, 's'}, {0xE0, 0xE5, 'a'}, {0xE7, 0xE7, 'c'},
{0xE8, 0xEB, 'e'}, {0xEC, 0xEF, 'i'}, {0xF1, 0xF1, 'n'}, {0xF2, 0xF6, 'o'},
{0xF8, 0xF8, 'o'}, {0xF9, 0xFC, 'u'}, {0xFD, 0xFD, 'y'}, {0xFF, 0xFF, 'y'},
{0x100, 0x105, 'a'}, {0x106, 0x10D, 'c'}, {0x10E, 0x111, 'd'}, {0x112, 0x11B, 'e'},
{0x11C, 0x123, 'g'}, {0x124, 0x127, 'h'}, {0x128, 0x131, 'i'}, {0x134, 0x135, 'j'},
{0x136, 0x138, 'k'}, {0x139, 0x142, 'l'}, {0x143, 0x149, 'n'}, {0x14C, 0x151, 'o'},
{0x154, 0x159, 'r'}, {0x15A, 0x161, 's'}, {0x162, 0x167, 't'}, {0x168, 0x173, 'u'},
{0x174, 0x175, 'w'}, {0x176, 0x178, 'y'}, {0x179, 0x17E, 'z'},
};
for (const auto& range : kRanges) {
if (code >= range.first && code <= range.last) {
return range.base;
}
}
return '?';
}
// Знаки вне А..я и Ё ё, у которых есть замена в CP866; 0 — замены нет.
// Украинские и белорусские Є є Ї ї Ў ў в CP866 есть (#F2..#F7). І і там
// нет — пишем латинские I i, они выглядят так же; Ґ ґ заменяем на Г г.
// Типографские апостроф, тире и кавычки бывают в названиях OpenStreetMap
// («Кам’янець-Подільський») — их заменяют знаки ASCII.
char cp866Extra(uint32_t code) {
static const struct {
uint16_t code;
uint8_t cp866;
} kExtra[] = {
{0x404, 0xF2}, {0x454, 0xF3}, {0x407, 0xF4}, {0x457, 0xF5}, // Є є Ї ї
{0x40E, 0xF6}, {0x45E, 0xF7}, // Ў ў
{0x406, 'I'}, {0x456, 'i'}, {0x490, 0x83}, {0x491, 0xA3}, // І і Ґ ґ
{0xB0, 0xF8}, // °
{0xA0, ' '}, // неразрывный пробел
{0x2BC, '\''}, {0x2018, '\''}, {0x2019, '\''}, // апострофы
{0x2013, '-'}, {0x2014, '-'}, // тире
{0xAB, '"'}, {0xBB, '"'}, {0x201C, '"'}, {0x201D, '"'}, {0x201E, '"'},
};
for (const auto& extra : kExtra) {
if (code == extra.code) {
return static_cast<char>(extra.cp866);
}
}
return 0;
}
// --- кодировки значений zifi.ini ----------------------------------------------------------
// Буква кириллицы по коду CP866 (#80..#FF); 0 — не буква (псевдографика, знаки).
uint32_t cp866Letter(uint8_t code) {
static const uint16_t kF0[] = {0x401, 0x451, 0x404, 0x454, 0x407, 0x457, 0x40E, 0x45E};
if (code >= 0x80 && code <= 0xAF) {
return 0x410 + (code - 0x80); // А..п
}
if (code >= 0xE0 && code <= 0xEF) {
return 0x440 + (code - 0xE0); // р..я
}
if (code >= 0xF0 && code <= 0xF7) {
return kF0[code - 0xF0]; // Ё ё Є є Ї ї Ў ў
}
return 0;
}
// Буква кириллицы по коду CP1251 (#80..#FF); 0 — не буква.
uint32_t cp1251Letter(uint8_t code) {
static const struct {
uint8_t code;
uint16_t letter;
} kExtra[] = {
{0xA8, 0x401}, {0xB8, 0x451}, {0xAA, 0x404}, {0xBA, 0x454}, // Ё ё Є є
{0xAF, 0x407}, {0xBF, 0x457}, {0xB2, 0x406}, {0xB3, 0x456}, // Ї ї І і
{0xA5, 0x490}, {0xB4, 0x491}, {0xA1, 0x40E}, {0xA2, 0x45E}, // Ґ ґ Ў ў
};
if (code >= 0xC0) {
return 0x410 + (code - 0xC0); // А..я
}
for (const auto& extra : kExtra) {
if (code == extra.code) {
return extra.letter;
}
}
return 0;
}
// Длина правильной последовательности UTF-8 с первого байта p (1..4);
// 0 — это не UTF-8 (одиночный байт продолжения, обрыв, «длинная» запись).
size_t utf8SequenceLength(const unsigned char* p) {
if (p[0] < 0x80) {
return 1;
}
size_t count;
if (p[0] >= 0xC2 && p[0] <= 0xDF) {
count = 2;
} else if (p[0] >= 0xE0 && p[0] <= 0xEF) {
count = 3;
} else if (p[0] >= 0xF0 && p[0] <= 0xF4) {
count = 4;
} else {
return 0;
}
for (size_t i = 1; i < count; ++i) {
if ((p[i] & 0xC0) != 0x80) {
return 0; // в том числе конец строки
}
}
if ((p[0] == 0xE0 && p[1] < 0xA0) || (p[0] == 0xED && p[1] >= 0xA0) ||
(p[0] == 0xF0 && p[1] < 0x90) || (p[0] == 0xF4 && p[1] >= 0x90)) {
return 0; // «длинная» запись или суррогат
}
return count;
}
} // namespace
void utf8ToCp866(const char* text, char* out, size_t capacity) {
if (capacity == 0) {
return;
}
size_t length = 0;
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
while (*p != 0 && length + 1 < capacity) {
uint32_t code;
if (*p < 0x80) {
code = *p++;
} else if ((*p & 0xE0) == 0xC0 && (p[1] & 0xC0) == 0x80) {
code = ((*p & 0x1F) << 6) | (p[1] & 0x3F);
p += 2;
} else if ((*p & 0xF0) == 0xE0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) {
code = ((*p & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
p += 3;
} else {
code = '?';
++p;
while ((*p & 0xC0) == 0x80) {
++p;
}
}
char c;
if (code < 0x80) {
c = static_cast<char>(code);
} else if (code >= 0x410 && code <= 0x43F) {
c = static_cast<char>(0x80 + (code - 0x410)); // А..п
} else if (code >= 0x440 && code <= 0x44F) {
c = static_cast<char>(0xE0 + (code - 0x440)); // р..я
} else if (code == 0x401) {
c = static_cast<char>(0xF0); // Ё
} else if (code == 0x451) {
c = static_cast<char>(0xF1); // ё
} else {
c = cp866Extra(code);
if (c == 0) {
c = latinBase(code);
}
}
out[length++] = c;
}
out[length] = 0;
}
void iniTextToUtf8(const char* text, char* out, size_t capacity) {
if (capacity == 0) {
return;
}
const unsigned char* src = reinterpret_cast<const unsigned char*>(text);
size_t length = 0;
// Уже UTF-8 (или чистый ASCII) — копируем целыми буквами.
bool utf8 = true;
for (const unsigned char* p = src; *p != 0;) {
const size_t count = utf8SequenceLength(p);
if (count == 0) {
utf8 = false;
break;
}
p += count;
}
if (utf8) {
for (const unsigned char* p = src; *p != 0;) {
const size_t count = utf8SequenceLength(p);
if (length + count >= capacity) {
break;
}
memcpy(out + length, p, count);
length += count;
p += count;
}
out[length] = 0;
return;
}
// Однобайтовая кириллица: CP866 или CP1251 — какая даёт больше букв.
// В CP866 прописные CP1251 (#C0..#DF) — псевдографика, а в CP1251 почти
// все буквы CP866 #80..#AF — знаки, поэтому ошибиться трудно. Поровну —
// CP866, родная кодировка Спектрума.
size_t letters866 = 0;
size_t letters1251 = 0;
for (const unsigned char* p = src; *p != 0; ++p) {
if (*p >= 0x80) {
letters866 += cp866Letter(*p) != 0 ? 1 : 0;
letters1251 += cp1251Letter(*p) != 0 ? 1 : 0;
}
}
const bool cp1251 = letters1251 > letters866;
for (const unsigned char* p = src; *p != 0; ++p) {
uint32_t code = *p;
if (code >= 0x80) {
code = cp1251 ? cp1251Letter(*p) : cp866Letter(*p);
if (code == 0) {
code = '?';
}
}
const size_t count = code < 0x80 ? 1 : 2; // кириллица в UTF-8 — два байта
if (length + count >= capacity) {
break;
}
appendUtf8(out, capacity, length, code);
}
out[length] = 0;
}
bool parseCitySearch(const char* json, size_t length, GeoResult& out, bool& notFound,
char* error, size_t errorSize) {
const char* end = json + length;
notFound = false;
const char* p = skipSpaces(json, end);
if (p >= end || *p != '{') {
setError(error, errorSize, "city: broken json");
return false;
}
// Ничего не нашёл — в ответе нет results: {"generationtime_ms":0.4}.
const char* results = findKey(json, end, "results");
if (results == nullptr) {
notFound = true;
setError(error, errorSize, "city: not found");
return false;
}
if (*results != '[') {
setError(error, errorSize, "city: broken json");
return false;
}
p = skipSpaces(results + 1, end);
if (p < end && *p == ']') {
notFound = true;
setError(error, errorSize, "city: not found");
return false;
}
// count=1: первое место списка — самое крупное из одноимённых.
const char* placeEnd = (p < end && *p == '{') ? skipValue(p, end) : nullptr;
if (placeEnd == nullptr) {
setError(error, errorSize, "city: broken json");
return false;
}
double latitude = 0.0;
double longitude = 0.0;
if (!objectNumber(p, placeEnd, "latitude", latitude) ||
!objectNumber(p, placeEnd, "longitude", longitude)) {
setError(error, errorSize, "city: no coordinates");
return false;
}
GeoResult result = {};
result.latitude = static_cast<float>(latitude);
result.longitude = static_cast<float>(longitude);
const char* name = findKey(p, placeEnd, "name");
if (name == nullptr || !parseString(name, placeEnd, result.place, sizeof(result.place))) {
result.place[0] = 0; // без названия — только погода
}
out = result;
return true;
}
bool parseZippopotam(const char* json, size_t length, GeoResult& out,
char* error, size_t errorSize) {
const char* end = json + length;
const char* places = findKey(json, end, "places");
if (places == nullptr || *places != '[') {
setError(error, errorSize, "zip: no places");
return false;
}
const char* p = places + 1;
bool found = false;
while (true) {
p = skipSpaces(p, end);
if (p >= end || *p == ']') {
break;
}
if (*p != '{') {
break;
}
const char* objectEnd = skipValue(p, end);
if (objectEnd == nullptr) {
break;
}
GeoResult candidate = {};
double latitude = 0.0;
double longitude = 0.0;
const char* name = findKey(p, objectEnd, "place name");
if (objectNumber(p, objectEnd, "latitude", latitude) &&
objectNumber(p, objectEnd, "longitude", longitude) &&
name != nullptr && parseString(name, objectEnd, candidate.place, sizeof(candidate.place))) {
candidate.latitude = static_cast<float>(latitude);
candidate.longitude = static_cast<float>(longitude);
out = candidate; // остаётся последнее место списка
found = true;
}
p = skipSpaces(objectEnd, end);
if (p < end && *p == ',') {
++p;
}
}
if (!found) {
setError(error, errorSize, "zip: no coordinates");
}
return found;
}
bool parseOpenMeteo(const char* json, size_t length, const char* placeUtf8,
uint8_t* record, char* error, size_t errorSize) {
const char* end = json + length;
memset(record, 0, kWeatherRecordSize);
record[kWrVersion] = kWeatherRecordVersion;
double offset = 0.0;
if (!objectNumber(json, end, "utc_offset_seconds", offset)) {
setError(error, errorSize, "meteo: no utc offset");
return false;
}
const int32_t offsetSeconds = static_cast<int32_t>(offset);
const char* current = findKey(json, end, "current");
const char* daily = findKey(json, end, "daily");
if (current == nullptr || *current != '{' || daily == nullptr || *daily != '{') {
setError(error, errorSize, "meteo: no current/daily");
return false;
}
const char* currentEnd = skipValue(current, end);
const char* dailyEnd = skipValue(daily, end);
if (currentEnd == nullptr || dailyEnd == nullptr) {
setError(error, errorSize, "meteo: broken json");
return false;
}
double temperature, code, isDay, wind, precipitation, pressure, dataTime;
if (!objectNumber(current, currentEnd, "temperature_2m", temperature) ||
!objectNumber(current, currentEnd, "weather_code", code) ||
!objectNumber(current, currentEnd, "is_day", isDay) ||
!objectNumber(current, currentEnd, "wind_speed_10m", wind) ||
!objectNumber(current, currentEnd, "precipitation", precipitation) ||
!objectNumber(current, currentEnd, "surface_pressure", pressure) ||
!objectNumber(current, currentEnd, "time", dataTime)) {
setError(error, errorSize, "meteo: current fields");
return false;
}
// double, а не float: unixtime ~1,8e9 во float (24 бита мантиссы) теряет
// до двух минут — восход и закат уезжали бы на минуту.
double times[kWeatherMaxDays], codes[kWeatherMaxDays], tmax[kWeatherMaxDays];
double tmin[kWeatherMaxDays], sums[kWeatherMaxDays];
double sunrise[kWeatherMaxDays], sunset[kWeatherMaxDays];
struct {
const char* key;
double* out;
} arrays[] = {
{"time", times}, {"weather_code", codes}, {"temperature_2m_max", tmax},
{"temperature_2m_min", tmin}, {"precipitation_sum", sums},
{"sunrise", sunrise}, {"sunset", sunset},
};
size_t count = kWeatherMaxDays;
for (const auto& array : arrays) {
const char* value = findKey(daily, dailyEnd, array.key);
const size_t got = value == nullptr ? 0 : parseNumberArray(value, dailyEnd, array.out,
kWeatherMaxDays);
if (got == 0) {
setError(error, errorSize, "meteo: daily fields");
return false;
}
if (got < count) {
count = got;
}
}
record[kWrStatus] = 1;
utf8ToCp866(placeUtf8 == nullptr ? "" : placeUtf8,
reinterpret_cast<char*>(record + kWrPlace), kWeatherPlaceLength);
record[kWrTemp] = static_cast<uint8_t>(clampI8(temperature));
record[kWrCode] = clampU8(code);
record[kWrIsDay] = isDay != 0.0 ? 1 : 0;
putU16(record + kWrWind10, clampU16(wind * 10.0));
putU16(record + kWrPrecip10, clampU16(precipitation * 10.0));
putU16(record + kWrPressure, clampU16(pressure * 0.750062)); // гПа -> мм рт. ст.
const LocalTime rise = localTime(static_cast<int64_t>(sunrise[0]), offsetSeconds);
const LocalTime set = localTime(static_cast<int64_t>(sunset[0]), offsetSeconds);
const LocalTime now = localTime(static_cast<int64_t>(dataTime), offsetSeconds);
record[kWrSunrise] = static_cast<uint8_t>(rise.hour);
record[kWrSunrise + 1] = static_cast<uint8_t>(rise.minute);
record[kWrSunset] = static_cast<uint8_t>(set.hour);
record[kWrSunset + 1] = static_cast<uint8_t>(set.minute);
record[kWrDataTime] = static_cast<uint8_t>(now.hour);
record[kWrDataTime + 1] = static_cast<uint8_t>(now.minute);
record[kWrDayCount] = static_cast<uint8_t>(count);
for (size_t i = 0; i < count; ++i) {
uint8_t* day = record + kWrDays + i * kWeatherDaySize;
// Полдень местного дня: unixtime полуночи плюс смещение уже даёт нужную
// дату, а полдень защищает от округления на границе суток.
const LocalTime date = localTime(static_cast<int64_t>(times[i]) + 43200, offsetSeconds);
day[0] = static_cast<uint8_t>(date.day);
day[1] = static_cast<uint8_t>(date.month);
day[2] = static_cast<uint8_t>(date.weekday);
day[3] = clampU8(codes[i]);
day[4] = static_cast<uint8_t>(clampI8(tmin[i]));
day[5] = static_cast<uint8_t>(clampI8(tmax[i]));
putU16(day + 6, clampU16(sums[i] * 10.0));
}
return true;
}
} // namespace zifi
+1527
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define CONNECT_TIMEOUT_MS (10000)
#define DEFAULT_SCAN_LIST_SIZE 64
void initialize_wifi();
int wf_scan(int timeout);
uint16_t wf_get_ap_num();
void wf_get_ap(int idx, uint8_t &auth, int8_t &rssi, uint8_t &chan, uint8_t *&ssid);
void get_ip(uint8_t *i, uint8_t *m, uint8_t *g);
bool wifi_connect(const char *ssid, const char *pass, int timeout_ms);
bool wifi_connect_saved(int timeout_ms);
bool wifi_is_enabled();
bool wifi_is_connected();
bool wifi_has_saved_ap();
void wifi_start_autoconnect();
void wifi_disconnect_now();
void esp_console_register_wifi_commands();
// Shared mutex serializing WiFi radio access between the SPI-slave path
// (main/helper.cpp) and the AT-command UART path (main/at_uart.cpp) - there is
// only one physical radio, so both interfaces must take this before touching
// esp_wifi_*/net.state.
extern SemaphoreHandle_t net_mtx;
bool wifi_connect_locked(const char *ssid, const char *pass, int timeout_ms);
void wifi_disconnect_now_locked();
int wf_scan_locked(int timeout);
// SNTP config, backing AT+CIPSNTPCFG / AT+CIPSNTPTIME (main/at_uart.cpp).
// timezone is a plain hour offset (-12..14, per ESP-AT), applied on top of the
// UTC time SNTP itself provides - not a full POSIX TZ. srv2/srv3 may be NULL.
void sntp_apply_config(bool enable, int timezone, const char *srv1, const char *srv2, const char *srv3);
void sntp_get_config(bool &enable, int &timezone, char *srv1, char *srv2, char *srv3, size_t buflen);
void sntp_format_time(char *buf, size_t buflen);
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// Разбор ответов погодных сервисов в двоичную запись для Z80.
//
// Модуль не зависит от Arduino: его собирает и host-тест на ПК
// (tests/test_weather_parse.py) на сохранённых ответах серверов.
// Формат записи един для прошивки, плагина (Weather screensaver/src/weather.asm)
// и эталона (Weather screensaver/tools/design.py); менять только вместе.
namespace zifi {
constexpr uint8_t kWeatherRecordVersion = 1;
constexpr size_t kWeatherPlaceLength = 24;
constexpr size_t kWeatherMaxDays = 6;
constexpr size_t kWeatherDaySize = 8;
// Смещения полей записи.
enum WeatherRecordLayout : size_t {
kWrStatus = 0, // 1 — данные есть
kWrVersion = 1, // kWeatherRecordVersion
kWrPlace = 2, // название места, CP866, ноль в конце, 24 байта
kWrTemp = 26, // температура, °C, знаковый байт
kWrCode = 27, // код погоды WMO
kWrIsDay = 28, // 1 — день
kWrWind10 = 29, // ветер, км/ч x10, u16
kWrPrecip10 = 31, // осадки за час, мм x10, u16
kWrPressure = 33, // давление у поверхности, мм рт. ст., u16
kWrSunrise = 35, // час, минута
kWrSunset = 37, // час, минута
kWrDataTime = 39, // местное время данных: час, минута
kWrDayCount = 41, // число дневных записей
kWrDays = 42, // дневные записи по 8 байт: число, месяц, день недели
// (0 — понедельник), код WMO, tmin, tmax, осадки x10 u16
};
constexpr size_t kWeatherRecordSize = kWrDays + kWeatherDaySize * kWeatherMaxDays;
struct GeoResult {
float latitude;
float longitude;
char place[64]; // UTF-8, название места для экрана
};
// Ответ геокодера Open-Meteo на поиск по названию
// (geocoding-api.open-meteo.com/v1/search?name=…&count=1&language=en|ru):
// координаты и название места на языке запроса («Kyiv», «Рим»; у маленьких
// мест без перевода — местное название). notFound=true — в ответе
// нет results: такого названия геокодер не знает, повтор не поможет.
bool parseCitySearch(const char* json, size_t length, GeoResult& out, bool& notFound,
char* error, size_t errorSize);
// Ответ api.zippopotam.us/<страна>/<индекс> (поиск по почтовому индексу,
// ключ zip:): координаты и название места (берётся последнее место списка —
// для итальянских CAP это коммуна).
bool parseZippopotam(const char* json, size_t length, GeoResult& out,
char* error, size_t errorSize);
// Значение из zifi.ini -> UTF-8 (в out, с нулём в конце). Файл могли
// сохранить в UTF-8 (редактор на ПК), в CP866 (редактор Wild Commander) или
// в CP1251 (Блокнот Windows «ANSI»). Правильный UTF-8 остаётся как есть,
// иначе берётся та из CP866 и CP1251, в которой больше кириллических букв.
void iniTextToUtf8(const char* text, char* out, size_t capacity);
// Ответ api.open-meteo.com/v1/forecast (current + daily, timeformat=unixtime,
// timezone=auto) -> запись kWeatherRecordSize байт. placeUtf8 — название
// места в UTF-8, как его дал справочник; в CP866 оно переводится только
// здесь, один раз (повторный перевод CP866 превращал кириллицу в «??»).
bool parseOpenMeteo(const char* json, size_t length, const char* placeUtf8,
uint8_t* record, char* error, size_t errorSize);
// UTF-8 -> CP866: кириллица переводится (с украинскими и белорусскими
// буквами), латиница с диакритикой упрощается до базовой буквы, типографские
// апостроф, тире и кавычки — до знаков ASCII, остальное заменяется '?'.
// Строка обрезается по capacity.
void utf8ToCp866(const char* text, char* out, size_t capacity);
} // namespace zifi
+435
View File
@@ -0,0 +1,435 @@
const u8 zx_screen[6912] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0x9F, 0x00, 0xFD, 0xE6,
0x00, 0x07, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x3E, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00,
0x00, 0xF0, 0x3B, 0xFC, 0x60, 0x80, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xC7, 0xE0, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x30,
0x00, 0x02, 0x00, 0x09, 0xC0, 0x2F, 0xF0, 0xC8, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xA0, 0x63, 0xC4, 0x00, 0xFC, 0xD0, 0x0D, 0x40, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x18, 0x00, 0x00, 0x4F, 0x8C, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x61, 0xC0, 0x40, 0x13, 0xE0, 0xE0, 0x78, 0x2F, 0x81, 0xC2, 0x05, 0xC6,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x30, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0C, 0x00, 0x00, 0x89, 0x03, 0x89, 0x1F, 0x70, 0x00, 0x02, 0xAD, 0xEF, 0xFD, 0xF8,
0x10, 0x0E, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x63, 0xBF, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x23, 0xD9, 0x89, 0xCC, 0x6A, 0x0F, 0x00, 0x02, 0x03, 0xA4, 0x5D, 0xAC,
0xFC, 0x00, 0x00, 0x07, 0x00, 0xFF, 0x00, 0x14, 0x07, 0x49, 0xE1, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xF7, 0x23, 0x20, 0xD3, 0xC9,
0x0C, 0x0C, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x78, 0xFC, 0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00,
0x00, 0xC0, 0x67, 0xE1, 0x80, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x1D, 0x11, 0x00, 0x00, 0x0D, 0xE0, 0x00, 0x06, 0x60,
0x00, 0x00, 0x02, 0x07, 0x08, 0x8F, 0xC0, 0xF0, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x90, 0xF8, 0x27, 0xC8, 0x40, 0xF0, 0xE0, 0x0A, 0x80, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x20, 0x00, 0x00, 0x1E, 0x31, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x82, 0x80, 0x11, 0x26, 0x24, 0xE0, 0x38, 0x1F, 0x11, 0x44, 0x03, 0xC8,
0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x10, 0x30, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x00, 0x30, 0x05, 0x55, 0x82, 0x9F, 0xB0, 0x00, 0x01, 0x5A, 0x5B, 0xFC, 0xA0,
0x00, 0x1C, 0xFF, 0x00, 0x00, 0x01, 0x00, 0xC7, 0xFC, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x5B, 0x1D, 0xA4, 0x64, 0x8F, 0x40, 0x48, 0x35, 0x10, 0xAB, 0xA0,
0xF0, 0x02, 0x00, 0x0E, 0x04, 0xFF, 0x00, 0x0C, 0x43, 0xDA, 0x84, 0x08, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0x07, 0xC0, 0x83, 0x03,
0x38, 0x18, 0xC1, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x81, 0xE0, 0xF7, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0x00,
0x01, 0x80, 0x8F, 0x48, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0E, 0x10, 0x00, 0x01, 0x03, 0xC0, 0x00, 0x1F, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x10, 0x37, 0x00, 0xE0, 0x08, 0xF8, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xF4, 0x26, 0x89, 0x00, 0xF0, 0xD0, 0x05, 0x40, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x78, 0xC1, 0x18, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x01, 0x06, 0x09, 0x0C, 0x28, 0xA0, 0x78, 0x2F, 0x08, 0xC2, 0x0B, 0xC0,
0xC0, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0x02, 0x00, 0x60, 0xEC, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x71, 0xC5, 0x46, 0x86, 0x9E, 0x70, 0x00, 0x08, 0x00, 0x25, 0xFE, 0x80,
0x07, 0x20, 0xFF, 0x00, 0x02, 0x02, 0x00, 0x8A, 0x71, 0x06, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xCE, 0x73, 0x2C, 0xE9, 0xC6, 0x8F, 0x04, 0x01, 0x01, 0x81, 0x11, 0xC0,
0xE6, 0x00, 0x00, 0x1C, 0x00, 0xFF, 0x01, 0x0A, 0x46, 0xB0, 0x08, 0x30, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x81, 0x06, 0x01,
0x70, 0x30, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x02, 0xDE, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x20,
0x02, 0x00, 0x1E, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7A, 0x22, 0x00, 0x06, 0x07, 0x80, 0x00, 0x78, 0x80,
0x00, 0x00, 0x00, 0x10, 0x60, 0xCC, 0x00, 0xC0, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xD0, 0x26, 0x4E, 0x10, 0xB0, 0xE0, 0x01, 0xC0, 0x00, 0x01,
0x00, 0xFF, 0x40, 0x00, 0x00, 0x01, 0xE2, 0x03, 0x20, 0x01, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x55, 0x19, 0x59, 0x70, 0x38, 0x05, 0x05, 0x60, 0x17, 0x80,
0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x41, 0xC1, 0x88, 0x02, 0x80, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x00, 0xE4, 0xC7, 0xAE, 0x0D, 0xB9, 0x70, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x82,
0x0D, 0xE1, 0xFF, 0x00, 0x18, 0x0C, 0x00, 0x05, 0x43, 0x1C, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x9C, 0x6B, 0x1D, 0xA0, 0xC4, 0x0F, 0x00, 0x12, 0x90, 0x28, 0x6B, 0xC0,
0xB8, 0x10, 0x00, 0x30, 0x00, 0xFF, 0x00, 0x88, 0x22, 0xB1, 0x13, 0xE0, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x19, 0x00, 0x08, 0x07, 0x0C, 0x00,
0xE0, 0xE0, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x08, 0x0C, 0xB0, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x38, 0x13, 0x38, 0x38, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9B, 0xC2, 0x00, 0x38, 0x0E, 0x00, 0x00, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0xE3, 0x30, 0x00, 0x00, 0x06, 0xF0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x45, 0x4C, 0x41, 0x70, 0xD0, 0x0A, 0xC0, 0x00, 0x00,
0x00, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x03, 0x1C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x21, 0x82, 0x15, 0xF3, 0xD0, 0x30, 0x4A, 0x00, 0xFD, 0x0E, 0xC0,
0xB0, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0x03, 0x87, 0x80, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xC0, 0x97, 0xAF, 0x4B, 0x30, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x06, 0x88,
0x17, 0xC0, 0xFF, 0x00, 0x30, 0x18, 0x00, 0x40, 0x46, 0x98, 0x46, 0x70, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x30, 0x2B, 0x39, 0x64, 0x80, 0x07, 0x00, 0x81, 0x05, 0x45, 0x81, 0x80,
0xF0, 0x40, 0x00, 0xC0, 0x00, 0xFF, 0x08, 0x24, 0x02, 0x69, 0x24, 0xC2, 0x00, 0x1E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x37, 0x00, 0x01, 0x1F, 0x00, 0x00,
0xCB, 0xC0, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x39, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x60, 0x01, 0x70, 0x5C, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x05, 0x8A, 0x44, 0x00, 0x70, 0x58, 0x00, 0x01, 0xE3, 0x00,
0x02, 0x00, 0x00, 0x03, 0xC6, 0x40, 0x03, 0x00, 0x4D, 0xE0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x09, 0x55, 0x5B, 0x83, 0x38, 0xE0, 0x04, 0x80, 0x58, 0x00,
0x08, 0xFF, 0x40, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x04, 0x06, 0x38, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x10, 0x07, 0x22, 0x2C, 0xA6, 0xF0, 0x40, 0x01, 0x0C, 0xDB, 0x87, 0xC3,
0xE0, 0x20, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x0F, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0xA6, 0xC3, 0x6C, 0xF0, 0x00, 0x02, 0x02, 0x02, 0x01, 0xC0,
0x1F, 0x98, 0xFF, 0x00, 0x00, 0x30, 0x00, 0xA2, 0x92, 0x36, 0xEC, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x40, 0x2A, 0x33, 0x70, 0xA4, 0x63, 0x00, 0x0A, 0x20, 0xBA, 0x88, 0x80,
0xC0, 0x00, 0x02, 0x00, 0x01, 0xFF, 0x00, 0x56, 0x23, 0x53, 0x4C, 0x8C, 0x00, 0x78, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF0, 0x8F, 0x00, 0x03, 0x7F, 0x20, 0x00,
0xD7, 0x81, 0x00, 0x00, 0x38, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x18, 0xE7, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x80, 0x03, 0xC3, 0xFB, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x69, 0x90, 0x44, 0x03, 0x08, 0x00, 0x0A, 0x04, 0x76, 0x00,
0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x0F, 0x00, 0x17, 0xC0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x06, 0x04, 0x0D, 0x5C, 0x07, 0x70, 0xD0, 0x81, 0xC0, 0x1E, 0x04,
0x60, 0xFF, 0x00, 0x00, 0x00, 0x10, 0x00, 0x1F, 0x02, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x44, 0x0C, 0x18, 0xF0, 0x00, 0x12, 0x2A, 0xAB, 0x4E, 0xCC,
0xC3, 0x81, 0xC0, 0x00, 0xFF, 0xFF, 0x11, 0x3C, 0x18, 0x94, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x11, 0x0E, 0x94, 0x87, 0x59, 0xF0, 0x04, 0x02, 0x23, 0xA8, 0x20, 0xC0,
0x3B, 0xC0, 0xFF, 0x00, 0x80, 0x01, 0x00, 0x04, 0x02, 0x56, 0xB9, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x22, 0x6B, 0x38, 0xCA, 0xC3, 0x00, 0x01, 0x09, 0x7C, 0x45, 0x00,
0x80, 0x00, 0x20, 0x00, 0x00, 0xFF, 0x00, 0x43, 0x22, 0x2F, 0x58, 0x38, 0x01, 0xF0, 0x08, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04,
0x02, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0F, 0xC0, 0x3F, 0x00, 0x00, 0xFF, 0x00, 0x00,
0xBF, 0x87, 0x81, 0x01, 0xF0, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x73, 0xCF, 0x40, 0x00, 0x01, 0xE0, 0xFF, 0xFF, 0x04,
0x00, 0x00, 0x00, 0x06, 0x9F, 0xAC, 0x03, 0x82, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x9D, 0xC0, 0x91, 0xC4, 0x08, 0x20, 0x20, 0x3F, 0x80, 0x28, 0x00,
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x37, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0x0F, 0x04, 0x9B, 0x78, 0x0F, 0x38, 0xE0, 0x0A, 0xC4, 0x1F, 0x93,
0xC0, 0xFF, 0x00, 0x00, 0x80, 0x00, 0x00, 0x3F, 0x00, 0x19, 0x38, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x40, 0x1E, 0x45, 0x09, 0x78, 0xD0, 0x00, 0x01, 0x15, 0x56, 0xB7, 0xBE,
0x07, 0x3F, 0x80, 0x00, 0xFF, 0xFF, 0x03, 0xFA, 0x31, 0x68, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x16, 0x18, 0xC2, 0x40, 0xF0, 0x02, 0x15, 0x55, 0x55, 0x62, 0x40,
0xF7, 0x00, 0xFF, 0x03, 0x00, 0x82, 0x02, 0x14, 0x03, 0x2C, 0xBE, 0xC2, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x92, 0x6B, 0xB4, 0x13, 0x03, 0x00, 0x44, 0x03, 0xFE, 0x80, 0x00,
0x00, 0x00, 0x06, 0x00, 0x08, 0xFF, 0x02, 0x32, 0x15, 0x1A, 0x48, 0x70, 0x03, 0xE0, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x6B, 0xB8, 0x80, 0x09, 0x00, 0x09, 0x12, 0x00, 0x59, 0x01,
0x00, 0xF7, 0xE3, 0x00, 0x90, 0x00, 0x00, 0x0C, 0x14, 0xC6, 0xB1, 0xE4, 0x0F, 0xC0, 0xE0, 0x00,
0x00, 0x01, 0xFE, 0x07, 0xD4, 0x11, 0x11, 0xE1, 0x58, 0x7C, 0x00, 0x09, 0x03, 0x0A, 0x12, 0x78,
0x38, 0xC0, 0xE0, 0x1C, 0xEE, 0x10, 0x00, 0x15, 0x29, 0xCA, 0x58, 0x70, 0x04, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x19, 0xE1, 0xC4, 0x86, 0x20, 0x18, 0x00, 0x13, 0x05, 0x5B, 0x18, 0x00,
0x80, 0x01, 0xF0, 0x60, 0x18, 0x00, 0x10, 0x8A, 0x25, 0x84, 0x81, 0x04, 0x10, 0x0C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x90, 0xD0, 0x86, 0x84, 0xB8, 0x78, 0x00, 0x09, 0x92, 0x00, 0x40, 0x80,
0x16, 0x00, 0x80, 0x5E, 0x80, 0x00, 0x72, 0x92, 0x11, 0xEC, 0x63, 0x91, 0x40, 0x0C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x18, 0x00, 0x09, 0x3A, 0x50, 0x00, 0x13, 0x47, 0xFF, 0x34, 0xC7,
0x06, 0xF0, 0x6E, 0x01, 0x01, 0x80, 0x00, 0x8A, 0x48, 0xA1, 0x64, 0x29, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x00, 0x02, 0x02, 0x43, 0x6C, 0xFD, 0xC0, 0x00, 0x09, 0x03, 0x63, 0x9A, 0xC1,
0x01, 0xC0, 0x00, 0x20, 0x0C, 0x0D, 0x00, 0xC9, 0x12, 0xA4, 0xC6, 0x20, 0xC8, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x16, 0x00, 0x18, 0x03, 0x13, 0x68, 0xC0, 0x00, 0x09, 0x84, 0x82, 0x07, 0x9B,
0x00, 0xC0, 0x80, 0x00, 0x80, 0x73, 0x00, 0x1C, 0x19, 0x2E, 0xC8, 0x89, 0x26, 0x33, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x02, 0x9F, 0x00, 0x00, 0x00, 0x02, 0x78, 0x80, 0xA2, 0x0C,
0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x82, 0x00, 0xDD, 0x05, 0xC2, 0xC6, 0xF0, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0xD8, 0x4B, 0x31, 0xD0, 0x73, 0x00, 0x02, 0x06, 0x00, 0x8C, 0x87,
0x00, 0xCF, 0x87, 0x01, 0x30, 0x02, 0x00, 0x82, 0x09, 0x44, 0xE0, 0xC0, 0x1F, 0xC1, 0xC0, 0x00,
0x00, 0x03, 0x3C, 0x0E, 0x64, 0x04, 0x98, 0xCD, 0x50, 0x7C, 0x00, 0x23, 0x14, 0xF7, 0x24, 0x40,
0x70, 0x72, 0x08, 0x70, 0xBC, 0x00, 0x11, 0x05, 0x95, 0xAA, 0xB2, 0x80, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1D, 0xCB, 0x70, 0x60, 0x0A, 0x41, 0x3C, 0x00, 0x07, 0x12, 0xED, 0x1A, 0x87,
0x0C, 0x66, 0xE0, 0x90, 0x30, 0x00, 0x84, 0x4A, 0x4D, 0x94, 0x40, 0x00, 0x10, 0x18, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x09, 0xF0, 0xA6, 0x8C, 0x41, 0xF0, 0x00, 0x82, 0x04, 0x00, 0x88, 0x80,
0x6C, 0x00, 0x80, 0xA0, 0xC0, 0x04, 0x12, 0x8A, 0x32, 0x58, 0x67, 0x33, 0x00, 0x38, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x14, 0x80, 0x02, 0xE8, 0xB8, 0x01, 0x0F, 0x12, 0x08, 0x09, 0xCE,
0x0C, 0xE0, 0x93, 0x08, 0x80, 0xC0, 0x00, 0x8A, 0x0C, 0xE1, 0x6C, 0x48, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x40, 0x0C, 0x0A, 0x63, 0x70, 0x68, 0xF0, 0x00, 0x02, 0x14, 0x21, 0x58, 0xC0,
0x02, 0xD0, 0x00, 0x10, 0x43, 0x1B, 0x00, 0x95, 0x21, 0x94, 0xE0, 0x23, 0x93, 0x60, 0x00, 0x00,
0x00, 0x08, 0x05, 0x03, 0x90, 0x18, 0x00, 0x2B, 0xC0, 0xC0, 0x00, 0x82, 0x31, 0x10, 0xC1, 0x69,
0x80, 0xC0, 0x40, 0x00, 0x40, 0x06, 0x04, 0x0E, 0x4C, 0xA3, 0xD1, 0x08, 0x4C, 0x66, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x32, 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x4B, 0x90, 0x80, 0x11, 0x65,
0x61, 0x00, 0x00, 0x18, 0x01, 0x80, 0x08, 0x00, 0x26, 0x0A, 0x86, 0x89, 0xE8, 0x20, 0x00, 0x00,
0x00, 0x00, 0x02, 0xC0, 0x31, 0x99, 0xCF, 0x31, 0x20, 0x83, 0x00, 0x24, 0x66, 0x00, 0x20, 0x1F,
0x00, 0x86, 0x0E, 0x83, 0x60, 0x00, 0x00, 0x00, 0x0D, 0x9B, 0x62, 0x90, 0x3D, 0x83, 0xB0, 0x00,
0x00, 0x06, 0x78, 0x18, 0x62, 0x16, 0xAC, 0x08, 0x70, 0x7C, 0x00, 0x09, 0x4C, 0x5A, 0x01, 0x00,
0xFC, 0xFC, 0x01, 0xC1, 0x78, 0x00, 0x08, 0x96, 0x41, 0x2C, 0x31, 0x08, 0x60, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x39, 0xC1, 0x68, 0x24, 0x44, 0x0C, 0x4C, 0x02, 0x0B, 0x81, 0x5C, 0x58, 0x9E,
0x39, 0xFC, 0xE0, 0x88, 0x80, 0x00, 0x00, 0x0A, 0x84, 0x82, 0x82, 0x10, 0x20, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x9A, 0xD8, 0x2C, 0x11, 0xE1, 0xF8, 0x00, 0x24, 0x06, 0x00, 0x61, 0x80,
0xD8, 0x00, 0x80, 0x07, 0x40, 0x00, 0x0B, 0x12, 0x19, 0x11, 0x4D, 0x26, 0x00, 0x70, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x18, 0x50, 0x8C, 0xC1, 0x18, 0x00, 0x07, 0x01, 0xA5, 0x40, 0xD8,
0x20, 0xC0, 0x09, 0x03, 0xE0, 0x00, 0x04, 0xC4, 0x84, 0xC3, 0x4C, 0x90, 0x4C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x60, 0x04, 0x28, 0x03, 0x50, 0xF4, 0xF0, 0x00, 0x24, 0x0D, 0x0C, 0xB2, 0xC0,
0x09, 0xE0, 0xC0, 0x10, 0x30, 0xF3, 0x12, 0x33, 0x22, 0xAC, 0xA0, 0x4E, 0x0C, 0x80, 0x00, 0x00,
0x00, 0x05, 0x2F, 0xA4, 0xE8, 0x10, 0x84, 0x88, 0x00, 0xC0, 0x00, 0x24, 0x12, 0xB8, 0x46, 0x22,
0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x01, 0x73, 0x42, 0x45, 0xA0, 0x30, 0x91, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x18, 0x04, 0x08, 0xCD, 0x03, 0x80, 0x00, 0x25, 0xB0, 0x40, 0x88, 0x20,
0xC9, 0x00, 0x00, 0x04, 0x01, 0x80, 0xC0, 0x00, 0x09, 0x05, 0xCC, 0xC3, 0x01, 0x80, 0x00, 0x00,
0x00, 0x00, 0x01, 0xC0, 0x73, 0x99, 0x8A, 0x74, 0x00, 0x83, 0x02, 0x09, 0x20, 0x00, 0x82, 0x8D,
0x00, 0x0C, 0x20, 0x05, 0xC0, 0x18, 0x00, 0x21, 0x05, 0x2A, 0xC4, 0x30, 0xF1, 0x03, 0x70, 0x00,
0x00, 0x08, 0x60, 0x20, 0x30, 0x32, 0x8A, 0x50, 0xE4, 0x20, 0x00, 0x17, 0x01, 0x94, 0xC8, 0x03,
0xE1, 0xF0, 0x43, 0x07, 0xC0, 0x00, 0x48, 0x12, 0x51, 0x05, 0x63, 0x10, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x72, 0x62, 0x70, 0xB0, 0x44, 0x07, 0x24, 0x00, 0x25, 0x0A, 0x97, 0x24, 0xB0,
0x3F, 0xF0, 0xC0, 0xA8, 0x00, 0x00, 0xC2, 0x24, 0x4C, 0x95, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0A, 0x04, 0xE6, 0xE8, 0x68, 0x32, 0x9E, 0xE0, 0x00, 0x09, 0x10, 0x80, 0xC4, 0x01,
0x1C, 0x00, 0x80, 0x09, 0x20, 0x00, 0xC9, 0x2B, 0x2C, 0x25, 0x48, 0x64, 0x00, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x20, 0x0C, 0x41, 0x14, 0x70, 0xC8, 0x00, 0x21, 0x4A, 0xD9, 0x88, 0x80,
0x00, 0xF0, 0x01, 0x80, 0x18, 0x70, 0x10, 0x48, 0x89, 0xA7, 0x98, 0x30, 0x5C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x2C, 0x84, 0x0C, 0x42, 0xF0, 0xA0, 0xF0, 0x00, 0x09, 0x50, 0x45, 0x65, 0xC0,
0x47, 0xE0, 0x20, 0x04, 0x0C, 0x07, 0x01, 0xE2, 0x91, 0xD8, 0x85, 0x4D, 0x62, 0x00, 0x00, 0x00,
0x00, 0x00, 0x98, 0x90, 0xC4, 0x10, 0x0A, 0x53, 0x00, 0xC0, 0x03, 0x09, 0xC4, 0x44, 0x0E, 0x80,
0x00, 0x80, 0x00, 0x00, 0x08, 0x01, 0x00, 0xCD, 0x85, 0x23, 0xC1, 0x31, 0x26, 0x30, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x0C, 0x0E, 0x04, 0x38, 0xC3, 0x00, 0x01, 0x0A, 0xA0, 0x80, 0x08, 0x83,
0xF1, 0x00, 0x80, 0x04, 0x03, 0x03, 0x98, 0x00, 0x02, 0xA3, 0x44, 0x4C, 0x16, 0x30, 0x00, 0x00,
0x00, 0x00, 0x07, 0x80, 0xBB, 0x89, 0x94, 0x6C, 0x09, 0x0B, 0x00, 0x04, 0x0A, 0x00, 0x50, 0x91,
0x01, 0x10, 0x01, 0x0F, 0x80, 0x30, 0x08, 0x31, 0x09, 0x29, 0x45, 0x61, 0x82, 0x04, 0x60, 0x00,
0x00, 0x10, 0xC0, 0x00, 0x58, 0x22, 0x56, 0xA2, 0xC9, 0x1C, 0x00, 0x81, 0x04, 0x07, 0x92, 0x3F,
0x9B, 0xC0, 0x08, 0x1E, 0x00, 0x00, 0x00, 0x82, 0x07, 0x97, 0x66, 0x41, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xC4, 0x22, 0x38, 0x10, 0xC9, 0x12, 0x70, 0x00, 0x0B, 0x00, 0xBE, 0x40, 0x80,
0x6F, 0xE0, 0xC0, 0x74, 0x00, 0x40, 0x91, 0x24, 0xC6, 0x88, 0x44, 0x20, 0xC4, 0x00, 0x00, 0x00,
0x00, 0x00, 0x21, 0x08, 0xC5, 0x6C, 0x44, 0x28, 0x33, 0xD8, 0x00, 0x04, 0x4A, 0x80, 0x50, 0x80,
0x38, 0x01, 0x80, 0x80, 0xE0, 0x00, 0x21, 0x06, 0x9D, 0x69, 0x54, 0x48, 0x05, 0xA0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x30, 0x18, 0x00, 0x80, 0x1E, 0xF8, 0x00, 0x0A, 0x00, 0xA8, 0x10, 0xC0,
0x04, 0xE0, 0x04, 0x80, 0xC4, 0x20, 0x30, 0x45, 0x15, 0x86, 0xD1, 0x33, 0xBC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x05, 0x0C, 0x2C, 0x40, 0xA2, 0xC8, 0xF0, 0x00, 0x04, 0x05, 0x02, 0x52, 0x80,
0x1E, 0xC0, 0x00, 0x04, 0x03, 0x0F, 0x00, 0xC2, 0xDC, 0xA8, 0x4A, 0xCA, 0xCC, 0x00, 0x04, 0x00,
0x00, 0x00, 0x03, 0x49, 0x70, 0x10, 0x04, 0x8F, 0x04, 0xC0, 0x07, 0x04, 0x48, 0x82, 0x24, 0x1B,
0x40, 0x80, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xCA, 0xF5, 0xA1, 0x78, 0x1C, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x1A, 0x00, 0x23, 0x03, 0x06, 0x03, 0xE0, 0x00, 0x04, 0x4D,
0xF0, 0x00, 0x00, 0x08, 0x02, 0x01, 0xFC, 0x00, 0x00, 0x45, 0x9A, 0xA3, 0x9B, 0x80, 0x00, 0x00,
0x00, 0x00, 0x2F, 0x01, 0xB3, 0x89, 0x90, 0xE0, 0x12, 0x13, 0x08, 0x41, 0x04, 0x80, 0xA0, 0x03,
0x00, 0x00, 0x03, 0x5E, 0x08, 0x40, 0x00, 0x08, 0x8B, 0x49, 0x2B, 0x40, 0x04, 0x00, 0x80, 0x00,
0x00, 0x01, 0x00, 0x81, 0x98, 0x47, 0x2B, 0xC6, 0x8B, 0x78, 0x22, 0x0A, 0x30, 0x8D, 0x08, 0x7F,
0xE6, 0x00, 0x00, 0x07, 0x41, 0x00, 0x22, 0x12, 0x13, 0x0B, 0xCC, 0xC3, 0x84, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x22, 0xB9, 0x32, 0x80, 0x29, 0xB8, 0x00, 0x1F, 0x05, 0x45, 0x30, 0x80,
0x87, 0x80, 0xC0, 0xFC, 0x00, 0x00, 0x69, 0xE5, 0x4B, 0x8A, 0x28, 0x44, 0x98, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0xB4, 0x40, 0x24, 0x04, 0x20, 0xE1, 0x78, 0x00, 0x43, 0x04, 0xC0, 0xA4, 0x80,
0x60, 0x01, 0x00, 0x60, 0xF8, 0x00, 0x51, 0x8D, 0x54, 0x44, 0xC8, 0x98, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 0x21, 0x81, 0x31, 0x70, 0x00, 0x01, 0x35, 0x41, 0x08, 0xC0,
0x18, 0xE0, 0x00, 0xC4, 0xF0, 0x00, 0x61, 0x2A, 0x03, 0x2E, 0x93, 0x63, 0x60, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x08, 0x02, 0x1D, 0xA0, 0xD0, 0x00, 0x41, 0x20, 0x91, 0xEC, 0xE0,
0x17, 0xC0, 0x80, 0x02, 0x00, 0xFD, 0x00, 0x51, 0x75, 0x59, 0x1C, 0xB0, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0xB6, 0x5C, 0x00, 0x03, 0x1F, 0x08, 0xC0, 0x02, 0x43, 0x11, 0x80, 0x01, 0x8D,
0xC8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xB0, 0x33, 0xC3, 0x72, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x0C, 0x30, 0x80, 0x03, 0x06, 0x0E, 0x46, 0xA0, 0x80, 0x04, 0x84,
0xA0, 0x01, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x01, 0x00, 0xA3, 0x71, 0x00, 0x7C, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x7E, 0x83, 0x59, 0x89, 0x91, 0x41, 0x26, 0x63, 0x00, 0x0A, 0x12, 0x80, 0x15, 0x0F,
0x31, 0x00, 0x06, 0x3C, 0x30, 0x00, 0x00, 0x85, 0x52, 0x89, 0x57, 0x82, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x2C, 0x0F, 0x8B, 0x8D, 0xD3, 0xF0, 0x00, 0x01, 0x29, 0x58, 0x19, 0x79,
0xC0, 0x00, 0x01, 0xE2, 0x00, 0x00, 0x10, 0x91, 0x05, 0x12, 0xD0, 0xC3, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x8E, 0x9D, 0x64, 0x50, 0x7A, 0xE0, 0x00, 0x45, 0x0A, 0x82, 0x02, 0x82,
0x0E, 0x00, 0xC0, 0xF2, 0x01, 0x00, 0x04, 0xB5, 0x22, 0x95, 0x58, 0xC1, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x68, 0x30, 0x30, 0x00, 0x42, 0xB6, 0xE8, 0x00, 0x0B, 0x12, 0xC0, 0x16, 0x80,
0xC0, 0x01, 0x00, 0x1C, 0xC4, 0x00, 0x0C, 0x92, 0x49, 0x4C, 0xD0, 0x34, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x02, 0x20, 0x20, 0x42, 0x89, 0x20, 0x60, 0x00, 0x44, 0x12, 0x94, 0x5A, 0xC0,
0x30, 0xE0, 0x00, 0x40, 0xD8, 0x08, 0x82, 0xAA, 0x11, 0x26, 0xC3, 0xE7, 0x19, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x84, 0x08, 0x48, 0xF8, 0x50, 0xC0, 0x00, 0x0A, 0x09, 0x38, 0x95, 0xF0,
0xAD, 0xE0, 0x40, 0x03, 0x04, 0x03, 0x00, 0xA2, 0xBB, 0x3D, 0x9D, 0xA1, 0x48, 0x80, 0x20, 0x00,
0x00, 0x00, 0x02, 0x08, 0x96, 0x00, 0x0E, 0x1F, 0x30, 0x80, 0x04, 0x0B, 0x26, 0x80, 0x80, 0xA0,
0xD0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xC4, 0x1E, 0xC1, 0xF1, 0xE1, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xE0, 0x0C, 0x06, 0xC2, 0x73, 0x00, 0x07, 0x15, 0x20, 0x00, 0x05, 0x93,
0x80, 0x80, 0x80, 0x11, 0x00, 0x0F, 0xF4, 0x38, 0x00, 0x56, 0xA2, 0x5B, 0x3E, 0x38, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x07, 0xA9, 0x03, 0x29, 0x95, 0x24, 0x41, 0x00, 0x01, 0x04, 0xC0, 0x80, 0x1E,
0xE3, 0x00, 0x31, 0x78, 0xE1, 0x00, 0x00, 0x62, 0x49, 0x89, 0x97, 0x18, 0x00, 0x00, 0x00, 0x00,
0x00, 0x44, 0x00, 0x04, 0x15, 0x2D, 0x8D, 0x06, 0x76, 0x84, 0x04, 0x4B, 0x02, 0xB7, 0xB8, 0x72,
0x00, 0x00, 0x07, 0xF9, 0x04, 0x00, 0x08, 0x91, 0x2B, 0x95, 0xA1, 0x86, 0x08, 0x00, 0x80, 0x00,
0x00, 0x00, 0x01, 0x00, 0x92, 0x0F, 0x46, 0x86, 0x71, 0x78, 0x00, 0x13, 0x27, 0x01, 0xC0, 0x00,
0x04, 0x00, 0xC0, 0xFE, 0x02, 0x00, 0x24, 0x96, 0x25, 0x89, 0x71, 0x88, 0x40, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0x10, 0x00, 0x0D, 0x40, 0x00, 0x05, 0x06, 0xC0, 0x80, 0x83,
0x01, 0x01, 0x00, 0x07, 0x02, 0x00, 0x0A, 0x89, 0x04, 0x1E, 0x40, 0x6A, 0x0A, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x08, 0x00, 0x42, 0x21, 0x83, 0x40, 0x90, 0x00, 0x13, 0x01, 0x48, 0x98, 0xC0,
0x60, 0xC0, 0x00, 0x21, 0xE4, 0x18, 0x00, 0x7A, 0x02, 0x6C, 0xA7, 0xEF, 0x26, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x22, 0x00, 0x08, 0x01, 0x6C, 0x80, 0xC0, 0x00, 0x01, 0x02, 0x44, 0xD2, 0xF6,
0x7F, 0xC0, 0x00, 0x01, 0x01, 0x0D, 0x00, 0x16, 0x11, 0x94, 0x13, 0x81, 0x90, 0x00, 0x80, 0x00,
0x00, 0x00, 0x01, 0x84, 0x0C, 0x02, 0x18, 0x0E, 0xE0, 0x80, 0x00, 0x05, 0x54, 0x40, 0x00, 0x02,
0xB0, 0x00, 0x00, 0x00, 0x02, 0x2D, 0x00, 0x03, 0x68, 0x0B, 0x87, 0xE7, 0x1C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x10, 0x52, 0x08, 0x19, 0x38, 0x7B, 0x00, 0x09, 0x08, 0xA0, 0x00, 0x05, 0x05,
0xC0, 0x01, 0x00, 0x50, 0x00, 0x07, 0xDB, 0xF0, 0x00, 0x0F, 0xE1, 0x0C, 0x61, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x60, 0x14, 0x08, 0xC1, 0x80, 0xC0, 0x00, 0x00, 0xA6, 0x20, 0x00, 0x04, 0x44,
0xC0, 0x01, 0x00, 0x02, 0x60, 0xF0, 0x00, 0x3F, 0x10, 0x13, 0x97, 0x38, 0x0E, 0x00, 0x00, 0x00,
0x00, 0x15, 0x40, 0x88, 0x40, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x05, 0x03,
0x3F, 0x3F, 0x00, 0xC2, 0x04, 0x07, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x28, 0xC0, 0x00, 0x00,
0x00, 0x22, 0xFB, 0x36, 0x21, 0x02, 0x40, 0x00, 0x00, 0x10, 0x00, 0x01, 0x06, 0x00, 0xFF, 0xFB,
0x76, 0x81, 0x00, 0x02, 0x04, 0x03, 0x30, 0x80, 0x8F, 0x83, 0xF3, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x89, 0x25, 0x20, 0x04, 0x00, 0x00, 0x07, 0xC1, 0x40, 0x00,
0x02, 0xF8, 0x00, 0x00, 0x08, 0x0A, 0xB1, 0x03, 0x08, 0xC2, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x6C, 0x14, 0x40, 0x40, 0x0B, 0x80, 0x02, 0x08, 0x48, 0x08,
0x00, 0xF8, 0x00, 0x00, 0x00, 0x03, 0x30, 0xC5, 0xFF, 0x00, 0x77, 0x96, 0x6C, 0x60, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x28, 0xEC, 0x3C, 0xFE, 0x77, 0x90, 0x04, 0x08, 0x67, 0x29,
0x6D, 0x20, 0x00, 0x0F, 0x00, 0x04, 0xF9, 0x7B, 0x42, 0xBB, 0xB0, 0x71, 0xB8, 0xC5, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x04, 0x20, 0x00, 0x51, 0x6F, 0xDC, 0x00,
0x57, 0xFE, 0x9A, 0xDE, 0xD5, 0x01, 0xFD, 0xFE, 0xDB, 0xFF, 0xEE, 0x68, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x42, 0x48, 0x64, 0x00, 0x80,
0x08, 0x09, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x10, 0x38, 0x32, 0x00, 0xC1, 0x40, 0xC0, 0x00, 0x00, 0x03, 0x20, 0x00, 0x05, 0x88,
0x81, 0x00, 0x00, 0x03, 0x20, 0xE0, 0x00, 0xFE, 0x41, 0x05, 0x2E, 0x16, 0x80, 0x20, 0x40, 0x00,
0x00, 0x01, 0x14, 0x6B, 0x04, 0x01, 0x00, 0xF0, 0x01, 0x00, 0x08, 0x48, 0x20, 0x00, 0x05, 0x01,
0x2F, 0x3F, 0x00, 0x00, 0x02, 0x07, 0x06, 0x10, 0x00, 0xC0, 0x00, 0x05, 0x76, 0x28, 0x00, 0x00,
0x00, 0x05, 0xFC, 0x6D, 0x11, 0x00, 0x20, 0x00, 0x00, 0x30, 0x62, 0x60, 0x65, 0x00, 0xDB, 0xAF,
0x24, 0x21, 0x00, 0x20, 0x12, 0x01, 0x01, 0x03, 0x7C, 0x87, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x8C, 0x17, 0x00, 0x00, 0x40, 0x06, 0x00, 0x04, 0xA4, 0x00,
0x80, 0xB8, 0x00, 0x04, 0x00, 0x16, 0x8C, 0x08, 0x20, 0xC0, 0x61, 0xFC, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x26, 0x01, 0x04, 0x00, 0x10, 0x42, 0x20, 0x01, 0x04, 0x80,
0x04, 0xB0, 0x00, 0x00, 0x40, 0x05, 0x30, 0x2B, 0x9F, 0x00, 0x0C, 0x1D, 0xC0, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06, 0x89, 0x25, 0x99, 0x3D, 0x00, 0xC6, 0x45, 0xB4, 0x84,
0xF4, 0x24, 0x81, 0x0E, 0x10, 0x02, 0x5D, 0xB9, 0x88, 0x6C, 0xE0, 0xB4, 0x1C, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x08, 0x04, 0x80, 0x32, 0x18, 0xA0, 0x99, 0xA6, 0x89,
0xCF, 0x73, 0x09, 0x88, 0x51, 0x84, 0xAB, 0xB5, 0xED, 0x2F, 0xB9, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x24, 0x40, 0x08, 0x08,
0x40, 0x00, 0x02, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x84, 0x68, 0x10, 0x82, 0x30, 0x00, 0x02, 0x02, 0x31, 0x20, 0x00, 0x05, 0x84,
0x11, 0x00, 0x00, 0x01, 0x04, 0xC0, 0x03, 0xF9, 0x8C, 0x0E, 0x90, 0x0D, 0x00, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x0A, 0x37, 0x00, 0x01, 0x18, 0xA0, 0x00, 0x03, 0x0E, 0x60, 0x20, 0x00, 0x05, 0x0C,
0x4D, 0x3F, 0x00, 0x01, 0x13, 0x03, 0x30, 0xC0, 0x04, 0xA0, 0x10, 0x01, 0xBC, 0x80, 0x00, 0x00,
0x00, 0x00, 0x6E, 0xD3, 0x01, 0x02, 0x00, 0x00, 0x00, 0x18, 0xB3, 0x30, 0x7A, 0x00, 0xEA, 0xC6,
0x89, 0x43, 0x00, 0x01, 0x02, 0x11, 0x00, 0xCC, 0xF9, 0x83, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x05, 0x9F, 0x42, 0x00, 0x21, 0x19, 0x10, 0x0F, 0x8D, 0x20,
0x20, 0xF0, 0x00, 0x00, 0x04, 0x07, 0xC3, 0xC0, 0x00, 0xC1, 0xCC, 0xF8, 0x28, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x80, 0x8E, 0xC2, 0x33, 0x58, 0x23, 0x10, 0x00, 0xA1, 0x02,
0x41, 0x70, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x3F, 0x20, 0x01, 0x0B, 0x60, 0x28, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x32, 0x09, 0x38, 0xFE, 0x23, 0x30, 0x66, 0x2A, 0x13,
0xA6, 0x08, 0x01, 0x0A, 0x58, 0x02, 0x75, 0xED, 0x84, 0x70, 0x24, 0x2E, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x04, 0x20, 0xC4, 0x11, 0x3E, 0x34, 0x80, 0xCB, 0x0C,
0x42, 0xA9, 0x00, 0xC4, 0xE5, 0x8C, 0x8C, 0xE2, 0x7C, 0x6F, 0x73, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x04,
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x28, 0x01, 0xB0, 0x00, 0x82, 0x08, 0x00, 0x03, 0x06, 0x1A, 0xA0, 0x00, 0x05, 0x40,
0xA1, 0x00, 0x00, 0x00, 0x82, 0x40, 0x0F, 0xC0, 0xF8, 0x05, 0x28, 0x0B, 0x86, 0x79, 0x00, 0x00,
0x00, 0x2B, 0x00, 0x4C, 0x24, 0x01, 0x3C, 0x90, 0x00, 0x03, 0x85, 0x20, 0x00, 0x00, 0x01, 0x24,
0x3B, 0x1F, 0x00, 0x01, 0x83, 0x03, 0x83, 0x80, 0xF0, 0xC0, 0x42, 0x12, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x44, 0xE0, 0x01, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x51, 0x03, 0x18, 0x82, 0x60, 0x12,
0x5A, 0x33, 0x00, 0x10, 0x40, 0x11, 0x00, 0x20, 0xE0, 0xC3, 0x22, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xCA, 0x24, 0x22, 0x0C, 0xB0, 0xEC, 0xD2, 0x40, 0x00,
0x60, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xC0, 0xE8, 0x81, 0xC0, 0x78, 0xF8, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC8, 0x03, 0x22, 0x19, 0x9E, 0x20, 0x80, 0x42, 0x10, 0x44,
0x28, 0x70, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x2D, 0x40, 0x04, 0x10, 0xC1, 0x70, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x73, 0x66, 0xCC, 0x09, 0x2A, 0xC9, 0x49,
0x2B, 0x21, 0x00, 0x40, 0x70, 0x03, 0x7E, 0xCA, 0x26, 0xEA, 0x10, 0x0B, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x40, 0x84, 0x1D, 0x18, 0x40, 0x2D, 0x64,
0x24, 0xCC, 0x44, 0x48, 0xA0, 0x89, 0x24, 0x93, 0x28, 0x45, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x5D, 0xBE, 0x80, 0x00, 0x80, 0x04, 0x00, 0x01, 0x04, 0x07, 0x20, 0x00, 0x04, 0x44,
0xE0, 0x41, 0x00, 0x08, 0xC0, 0x80, 0x3C, 0x01, 0x40, 0x0E, 0x90, 0x05, 0x23, 0x8E, 0x80, 0x00,
0x00, 0x06, 0x40, 0xD5, 0x24, 0x01, 0x3C, 0x80, 0x03, 0x15, 0x00, 0x00, 0x20, 0x00, 0x05, 0x00,
0x48, 0x0E, 0x00, 0x00, 0x89, 0x43, 0x1E, 0x0F, 0x80, 0x82, 0x80, 0x42, 0xE6, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x40, 0x81, 0x02, 0x00, 0x01, 0x08, 0x07, 0x00, 0x81, 0xC3, 0xA3, 0x00, 0x01,
0x8E, 0x23, 0x00, 0x10, 0x21, 0x18, 0x03, 0x00, 0x83, 0xC1, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x6F, 0x41, 0x71, 0x18, 0x07, 0xBC, 0x9F, 0x68, 0x00, 0x0C,
0x02, 0xF0, 0x00, 0x00, 0x00, 0x07, 0x42, 0x7E, 0x04, 0xC0, 0xE2, 0xF0, 0x58, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x4C, 0x30, 0x10, 0x80, 0x0F, 0xC0, 0x04, 0x10, 0x59, 0x20,
0x04, 0xF0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x10, 0x0B, 0xC0, 0x00, 0x01, 0x82, 0xE4, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x18, 0x44, 0x61, 0x55, 0xC3, 0xB8, 0x14, 0x94, 0x54, 0x80,
0x52, 0x55, 0x00, 0x88, 0xD8, 0x05, 0x6A, 0xD9, 0xB2, 0xA0, 0x41, 0x13, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x0E, 0x84, 0x69, 0x08, 0xD2,
0xB2, 0x44, 0x80, 0x00, 0x80, 0x00, 0x00, 0x21, 0x24, 0x20, 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x83, 0x3B, 0xC6, 0x18, 0x00, 0x00, 0x00, 0x80, 0x00, 0x60, 0x32, 0x00, 0x00, 0x05, 0x40,
0xC0, 0xE2, 0x00, 0x00, 0x48, 0xC0, 0x40, 0x07, 0x32, 0x0D, 0x40, 0x00, 0x4C, 0xF0, 0x00, 0x00,
0x00, 0x01, 0x18, 0xA4, 0x2E, 0x01, 0x3E, 0x00, 0x46, 0x00, 0x0C, 0x06, 0x00, 0x00, 0x01, 0x03,
0x15, 0x00, 0x00, 0x08, 0x09, 0x06, 0x3C, 0x3C, 0x00, 0x81, 0x08, 0x29, 0xCC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x99, 0x09, 0x00, 0x45, 0x2D, 0x80, 0xD4, 0x18, 0x93, 0x75, 0x00, 0x14,
0x53, 0x43, 0x00, 0x08, 0x30, 0x28, 0x0F, 0x01, 0x1C, 0x83, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x25, 0x18, 0x7B, 0x80, 0x03, 0x1F, 0x0A, 0x00, 0x82, 0x1A,
0x39, 0xE0, 0x00, 0x00, 0x00, 0x0B, 0x61, 0x3F, 0x00, 0xC0, 0x88, 0xF0, 0x54, 0x90, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x0E, 0xA4, 0x11, 0x08, 0x00, 0xC0, 0x01, 0x02, 0x08, 0xAA, 0x32,
0x80, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x08, 0x82, 0x00, 0xE1, 0x00, 0x16, 0x00, 0xD8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x0C, 0x78, 0x13, 0x4A, 0x74, 0x3A, 0xA1, 0xBA, 0x21,
0x45, 0xB2, 0x04, 0xC5, 0x68, 0x00, 0xB4, 0x94, 0xD1, 0xD4, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x12, 0x08, 0xAA, 0x20, 0x9B,
0x92, 0xA0, 0xA8, 0x20, 0x00, 0x90, 0x52, 0x00, 0x08, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xFF, 0xF0, 0x10, 0x00, 0x00, 0x11, 0x80, 0x08, 0x72, 0x3B, 0x20, 0x00, 0x04, 0x80,
0xE0, 0xE0, 0x00, 0x10, 0x00, 0xB4, 0x00, 0x0E, 0xFD, 0x16, 0x00, 0x00, 0x52, 0x10, 0x00, 0x00,
0x00, 0x04, 0x0E, 0x0E, 0x6E, 0x01, 0x7F, 0x00, 0x00, 0x00, 0x1F, 0x0F, 0x00, 0x00, 0x10, 0x09,
0x23, 0x40, 0x00, 0x00, 0x4C, 0x40, 0x81, 0xF7, 0x18, 0xC5, 0x60, 0x92, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFD, 0x01, 0x00, 0x63, 0x7F, 0xD2, 0xFD, 0x1E, 0xBC, 0x3E, 0xA0, 0x42,
0xB5, 0x17, 0x00, 0x08, 0x10, 0x0C, 0xE1, 0x0F, 0x61, 0x81, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x8C, 0x0D, 0x34, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x74, 0xE0, 0x00, 0x00, 0x80, 0x03, 0x64, 0x5D, 0xA1, 0x40, 0x40, 0xC0, 0xF5, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x80, 0x18, 0xF8, 0x02, 0x8D, 0x8D, 0x20, 0x26, 0x50, 0xCC, 0x66, 0x11,
0xA2, 0x79, 0x00, 0x80, 0x00, 0x00, 0x04, 0x21, 0x00, 0xC2, 0x00, 0x80, 0xD5, 0x70, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0xBC, 0x02, 0x05, 0xFA, 0x4D, 0x6B, 0x10, 0x04,
0x06, 0xEB, 0x00, 0xD7, 0x2C, 0x02, 0x11, 0x08, 0xEB, 0xC0, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x02, 0x02, 0x32, 0xAF,
0x48, 0x42, 0x0D, 0x00, 0x00, 0x02, 0x00, 0x10, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x2B, 0xEE, 0xF8, 0xE0, 0x00, 0x00, 0x0B, 0x00, 0x30, 0x31, 0x19, 0x00, 0x00, 0x05, 0x40,
0xC0, 0xC0, 0x00, 0x20, 0x24, 0xFC, 0x00, 0x20, 0xFA, 0x29, 0x00, 0x03, 0xAF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x01, 0x29, 0x5E, 0x01, 0x7F, 0x00, 0x10, 0x60, 0x2E, 0x07, 0x08, 0x00, 0x09, 0x00,
0x01, 0x81, 0x00, 0x04, 0x04, 0x01, 0x0F, 0x9F, 0xE0, 0x81, 0xC2, 0x3F, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x81, 0x37, 0xBE, 0xF7, 0x7B, 0xCB, 0x11, 0xDF, 0x60, 0x26,
0x08, 0x27, 0x00, 0x00, 0x08, 0x08, 0x3F, 0x79, 0x0F, 0xC1, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x3E, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04,
0x00, 0xE8, 0x00, 0x00, 0x80, 0x03, 0x21, 0x8B, 0xFB, 0x00, 0x00, 0x80, 0xD9, 0x20, 0x00, 0x00,
0x00, 0x00, 0x00, 0xA0, 0x00, 0x20, 0xAD, 0x40, 0xE6, 0xD8, 0x0D, 0x03, 0x32, 0x64, 0xD5, 0xA5,
0x34, 0xEB, 0x00, 0xA0, 0x00, 0x00, 0x04, 0x09, 0x04, 0xFF, 0x00, 0x23, 0x82, 0x18, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x7F, 0x1C, 0xBF, 0x04, 0x00,
0x82, 0xFD, 0x90, 0xFF, 0x04, 0x04, 0x00, 0x00, 0xFE, 0x90, 0x00, 0x4F, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x10, 0xFD,
0x91, 0x10, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x00, 0x00, 0x00, 0x41,
0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x41, 0x48, 0x48,
0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x4D, 0x4D, 0x41, 0x41, 0x4D,
0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D,
0x4D, 0x48, 0x4D, 0x48, 0x48, 0x48, 0x48, 0x41, 0x01, 0x01, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x41, 0x48, 0x41, 0x41, 0x48, 0x48, 0x41, 0x48, 0x4D, 0x4D, 0x4D, 0x4D,
0x4D, 0x41, 0x4D, 0x48, 0x4D, 0x48, 0x48, 0x48, 0x01, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x41, 0x41, 0x41, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x4D, 0x4D, 0x4D, 0x5D,
0x4B, 0x4B, 0x4B, 0x4B, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x41, 0x01, 0x41, 0x41, 0x45, 0x45, 0x4D,
0x4B, 0x4B, 0x41, 0x4B, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x01, 0x41, 0x4D, 0x68, 0x69, 0x59,
0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x41, 0x48, 0x48, 0x48, 0x41, 0x48, 0x41, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x01, 0x41, 0x4D, 0x43, 0x69, 0x59,
0x4B, 0x59, 0x59, 0x4B, 0x4B, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x01, 0x01, 0x01, 0x00,
0x00, 0x01, 0x01, 0x01, 0x41, 0x41, 0x41, 0x41, 0x48, 0x41, 0x01, 0x41, 0x4D, 0x4D, 0x69, 0x59,
0x59, 0x59, 0x59, 0x59, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
0x00, 0x00, 0x41, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x43, 0x01, 0x41, 0x4D, 0x45, 0x69, 0x5D,
0x5D, 0x5D, 0x43, 0x48, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x58, 0x43, 0x00, 0x41, 0x4D, 0x43, 0x69, 0x5D,
0x5D, 0x58, 0x43, 0x41, 0x48, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00,
0x00, 0x00, 0x41, 0x43, 0x41, 0x41, 0x43, 0x43, 0x58, 0x43, 0x01, 0x41, 0x4D, 0x69, 0x69, 0x5D,
0x5D, 0x43, 0x41, 0x41, 0x48, 0x48, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x48, 0x41, 0x41, 0x00, 0x00,
0x00, 0x00, 0x41, 0x43, 0x43, 0x43, 0x43, 0x58, 0x73, 0x46, 0x00, 0x41, 0x4D, 0x4D, 0x69, 0x5D,
0x5E, 0x46, 0x41, 0x41, 0x01, 0x41, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
0x00, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x46, 0x70, 0x46, 0x41, 0x41, 0x4D, 0x45, 0x45, 0x4D,
0x71, 0x46, 0x41, 0x00, 0x01, 0x41, 0x48, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00,
0x00, 0x00, 0x42, 0x42, 0x42, 0x46, 0x46, 0x70, 0x70, 0x01, 0x05, 0x41, 0x05, 0x41, 0x01, 0x45,
0x71, 0x46, 0x46, 0x01, 0x46, 0x43, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00,
0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x70, 0x70, 0x46, 0x41, 0x05, 0x41, 0x45, 0x00, 0x01, 0x45,
0x71, 0x46, 0x00, 0x01, 0x41, 0x58, 0x59, 0x59, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00,
0x00, 0x42, 0x56, 0x50, 0x46, 0x46, 0x70, 0x46, 0x01, 0x05, 0x45, 0x45, 0x05, 0x00, 0x41, 0x45,
0x4E, 0x70, 0x00, 0x01, 0x41, 0x43, 0x5E, 0x5E, 0x5E, 0x4B, 0x4B, 0x4B, 0x43, 0x43, 0x00, 0x00,
0x00, 0x42, 0x42, 0x46, 0x70, 0x41, 0x41, 0x41, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x68, 0x68,
0x69, 0x68, 0x00, 0x01, 0x41, 0x43, 0x5E, 0x73, 0x5E, 0x4B, 0x59, 0x58, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x41, 0x4D, 0x69, 0x69, 0x68, 0x68, 0x68, 0x69, 0x68, 0x68,
0x68, 0x45, 0x00, 0x01, 0x01, 0x45, 0x69, 0x71, 0x71, 0x4E, 0x5E, 0x43, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x01, 0x41, 0x48, 0x4D, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69,
0x69, 0x45, 0x00, 0x45, 0x01, 0x45, 0x69, 0x69, 0x69, 0x4D, 0x4B, 0x48, 0x08, 0x01, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x41, 0x41, 0x41, 0x45, 0x45, 0x45, 0x68, 0x69, 0x69, 0x4D, 0x4D,
0x4D, 0x69, 0x41, 0x69, 0x45, 0x45, 0x4D, 0x4D, 0x69, 0x4D, 0x4D, 0x48, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x41, 0x01, 0x41, 0x05, 0x45, 0x41, 0x41, 0x48,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x01, 0x01, 0x41,
0x01, 0x01, 0x41, 0x00, 0x01, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};