Projects
Everything I build on the channel, with the parts and the code.
Pick a project, grab the components and copy the code. That's it.
- 01Arduino ConsoleGIGA R1 · C++
- 02Vinyl PlayerGIGA R1 · C++
- 03Next buildSoon
Arduino Console
A pocket arcade on an Arduino GIGA with a 4" touchscreen. Two games built in: Sky Hop, where a bird dodges pillars, and UFO Strike, a space shooter.
Components 04
- 01
Arduino GIGA R1 WiFi
Runs both games and keeps every frame in its SDRAM
- 02
4" touchscreen
320 × 480, ST7796S display with FT6336U touch
- 03
Breadboard
To wire it all up without soldering
- 04
Jumper wires
From the screen to the board
Libraries
- GFX Library for Arduino
#include <Arduino_GFX_Library.h>
#include <SPI.h>
#include <Wire.h>
#include <SDRAM.h>
#define TFT_CS 10
#define TFT_DC 6
#define TFT_RST 7
#define TFT_BL 5
#define CTP_RST 4
#define CTP_INT 3
#define CTP_ADDR 0x38
#define TFT_ROTATION 2
#define TFT_IPS false
#define TOUCH_SWAP_XY false
#define TOUCH_FLIP_X true
#define TOUCH_FLIP_Y true
#define SPI_HZ 24000000
#define SCR_W 320
#define SCR_H 480
#define STEP_MS 16
#define TW 4
#define TH 8
#define TCOLS (SCR_W / TW)
#define TROWS (SCR_H / TH)
#define GAMES 2
constexpr uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
const uint16_t C_BLACK = 0x0000;
const uint16_t C_WHITE = 0xFFFF;
class GigaSPI1Bus : public Arduino_DataBus {
public:
GigaSPI1Bus(int8_t dc, int8_t cs) : _dc(dc), _cs(cs) {}
bool begin(int32_t speed = GFX_NOT_DEFINED, int8_t dataMode = GFX_NOT_DEFINED) {
_speed = (speed == GFX_NOT_DEFINED) ? 24000000 : speed;
::pinMode(_dc, OUTPUT);
::digitalWrite(_dc, HIGH);
::pinMode(_cs, OUTPUT);
::digitalWrite(_cs, HIGH);
SPI1.begin();
return true;
}
void beginWrite() {
SPI1.beginTransaction(SPISettings(_speed, MSBFIRST, SPI_MODE0));
::digitalWrite(_dc, HIGH);
::digitalWrite(_cs, LOW);
}
void endWrite() {
::digitalWrite(_cs, HIGH);
SPI1.endTransaction();
}
void writeCommand(uint8_t c) {
::digitalWrite(_dc, LOW);
SPI1.transfer(c);
::digitalWrite(_dc, HIGH);
}
void writeCommand16(uint16_t c) {
::digitalWrite(_dc, LOW);
_buf[0] = c >> 8;
_buf[1] = c & 0xFF;
SPI1.transfer(_buf, 2);
::digitalWrite(_dc, HIGH);
}
void writeCommandBytes(uint8_t* data, uint32_t len) {
::digitalWrite(_dc, LOW);
sendBytes(data, len);
::digitalWrite(_dc, HIGH);
}
void write(uint8_t d) {
SPI1.transfer(d);
}
void write16(uint16_t d) {
_buf[0] = d >> 8;
_buf[1] = d & 0xFF;
SPI1.transfer(_buf, 2);
}
void writeRepeat(uint16_t p, uint32_t len) {
while (len) {
uint32_t n = len > BUF_PIX ? BUF_PIX : len;
for (uint32_t i = 0; i < n; i++) {
_buf[i * 2] = p >> 8;
_buf[i * 2 + 1] = p & 0xFF;
}
SPI1.transfer(_buf, n * 2);
len -= n;
}
}
void writePixels(uint16_t* data, uint32_t len) {
while (len) {
uint32_t n = len > BUF_PIX ? BUF_PIX : len;
for (uint32_t i = 0; i < n; i++) {
_buf[i * 2] = data[i] >> 8;
_buf[i * 2 + 1] = data[i] & 0xFF;
}
SPI1.transfer(_buf, n * 2);
data += n;
len -= n;
}
}
void writeBytes(uint8_t* data, uint32_t len) {
sendBytes(data, len);
}
void writeIndexedPixels(uint8_t* data, uint16_t* idx, uint32_t len) {
while (len--) write16(idx[*data++]);
}
void writeIndexedPixelsDouble(uint8_t* data, uint16_t* idx, uint32_t len) {
while (len--) {
uint16_t p = idx[*data++];
write16(p);
write16(p);
}
}
private:
static const uint32_t BUF_PIX = 512;
int8_t _dc, _cs;
int32_t _speed = 24000000;
uint8_t _buf[BUF_PIX * 2];
void sendBytes(uint8_t* data, uint32_t len) {
while (len) {
uint32_t n = len > sizeof(_buf) ? sizeof(_buf) : len;
memcpy(_buf, data, n);
SPI1.transfer(_buf, n);
data += n;
len -= n;
}
}
};
GigaSPI1Bus* bus = new GigaSPI1Bus(TFT_DC, TFT_CS);
Arduino_TFT* tft = new Arduino_ST7796(bus, TFT_RST, TFT_ROTATION, TFT_IPS);
class FrameCanvas : public Arduino_GFX {
public:
uint16_t* buf = nullptr;
FrameCanvas() : Arduino_GFX(SCR_W, SCR_H) {}
bool begin(int32_t speed = GFX_NOT_DEFINED) override { return true; }
void writePixelPreclipped(int16_t x, int16_t y, uint16_t c) override {
buf[(int32_t)y * SCR_W + x] = c;
}
void writeFillRectPreclipped(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t c) {
for (int16_t j = 0; j < h; j++) {
uint16_t* p = buf + (int32_t)(y + j) * SCR_W + x;
for (int16_t i = 0; i < w; i++) p[i] = c;
}
}
};
FrameCanvas canvas;
uint16_t* scrBuf = nullptr;
uint16_t* bgBuf = nullptr;
uint16_t* savedBuf = nullptr;
void fillR(int x, int y, int w, int h, uint16_t c) {
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > SCR_W) w = SCR_W - x;
if (y + h > SCR_H) h = SCR_H - y;
if (w <= 0 || h <= 0) return;
for (int j = 0; j < h; j++) {
uint16_t* p = canvas.buf + (y + j) * SCR_W + x;
for (int i = 0; i < w; i++) p[i] = c;
}
}
uint16_t darken(uint16_t c) { return (c >> 1) & 0x7BEF; }
uint16_t mix(uint16_t a, uint16_t b, int t) {
int ar = a >> 11, ag = (a >> 5) & 63, ab = a & 31;
int br = b >> 11, bg = (b >> 5) & 63, bb = b & 31;
int r = ar + (br - ar) * t / 256;
int g = ag + (bg - ag) * t / 256;
int bl = ab + (bb - ab) * t / 256;
return (r << 11) | (g << 5) | bl;
}
void gradient(int y0, int y1, uint16_t c0, uint16_t c1) {
for (int y = y0; y < y1; y++) fillR(0, y, SCR_W, 1, mix(c0, c1, (y - y0) * 256 / (y1 - y0)));
}
void text(int x, int y, const char* s, int sz, uint16_t c) {
canvas.setTextSize(sz);
canvas.setTextColor(c);
canvas.setCursor(x, y);
canvas.print(s);
}
int textW(const char* s, int sz) { return strlen(s) * 6 * sz; }
void textC(int cx, int y, const char* s, int sz, uint16_t c) { text(cx - textW(s, sz) / 2, y, s, sz, c); }
void textCO(int cx, int y, const char* s, int sz, uint16_t c, uint16_t o) {
int x = cx - textW(s, sz) / 2;
for (int dx = -2; dx <= 2; dx += 2)
for (int dy = -2; dy <= 2; dy += 2)
if (dx || dy) text(x + dx, y + dy, s, sz, o);
text(x, y, s, sz, c);
}
bool hit(int px, int py, int x, int y, int w, int h) {
return px >= x && px < x + w && py >= y && py < y + h;
}
void bgBegin() { savedBuf = canvas.buf; canvas.buf = bgBuf; }
void bgEnd() { canvas.buf = savedBuf; }
struct Span { int16_t x0, x1, y0, y1; };
void pushRect(int tx0, int tx1, int ty0, int ty1) {
int x = tx0 * TW, y = ty0 * TH;
int w = (tx1 - tx0 + 1) * TW, h = (ty1 - ty0 + 1) * TH;
tft->startWrite();
tft->writeAddrWindow(x, y, w, h);
for (int r = 0; r < h; r++) bus->writePixels(canvas.buf + (y + r) * SCR_W + x, w);
tft->endWrite();
}
void present() {
static Span open[TCOLS], next[TCOLS];
static uint8_t dirty[TCOLS];
int nOpen = 0;
for (int ty = 0; ty < TROWS; ty++) {
memset(dirty, 0, TCOLS);
for (int r = 0; r < TH; r++) {
int off = (ty * TH + r) * SCR_W;
const uint32_t* a = (const uint32_t*)(canvas.buf + off);
const uint32_t* b = (const uint32_t*)(scrBuf + off);
for (int tx = 0; tx < TCOLS; tx++, a += 2, b += 2)
if (a[0] != b[0] || a[1] != b[1]) dirty[tx] = 1;
}
int nNext = 0, tx = 0;
while (tx < TCOLS) {
if (!dirty[tx]) { tx++; continue; }
int s = tx, e = tx;
tx++;
while (tx < TCOLS) {
if (dirty[tx]) { e = tx; tx++; }
else if (tx + 1 < TCOLS && dirty[tx + 1]) { e = tx + 1; tx += 2; }
else break;
}
int y0 = ty;
for (int i = 0; i < nOpen; i++) {
if (open[i].x0 == s && open[i].x1 == e) { y0 = open[i].y0; open[i].x0 = -1; break; }
}
next[nNext++] = { (int16_t)s, (int16_t)e, (int16_t)y0, (int16_t)ty };
}
for (int i = 0; i < nOpen; i++)
if (open[i].x0 >= 0) pushRect(open[i].x0, open[i].x1, open[i].y0, open[i].y1);
memcpy(open, next, nNext * sizeof(Span));
nOpen = nNext;
}
for (int i = 0; i < nOpen; i++) pushRect(open[i].x0, open[i].x1, open[i].y0, open[i].y1);
uint16_t* t = scrBuf;
scrBuf = canvas.buf;
canvas.buf = t;
}
bool prevTouch = false;
struct Input { bool tDown, tHeld; int tx, ty; } in = {};
bool readTouch(int& x, int& y) {
Wire.beginTransmission(CTP_ADDR);
Wire.write(0x02);
if (Wire.endTransmission(false) != 0) return false;
Wire.requestFrom(CTP_ADDR, 5);
if (Wire.available() < 5) return false;
uint8_t n = Wire.read() & 0x0F;
uint8_t xh = Wire.read(), xl = Wire.read(), yh = Wire.read(), yl = Wire.read();
if (n == 0 || n > 2) return false;
x = ((xh & 0x0F) << 8) | xl;
y = ((yh & 0x0F) << 8) | yl;
if (TOUCH_SWAP_XY) { int t = x; x = y; y = t; }
if (TOUCH_FLIP_X) x = SCR_W - 1 - x;
if (TOUCH_FLIP_Y) y = SCR_H - 1 - y;
return true;
}
void readInput() {
int x, y;
bool t = readTouch(x, y);
if (t) { in.tx = x; in.ty = y; }
if (t && !prevTouch) in.tDown = true;
in.tHeld = t;
prevTouch = t;
}
struct Part { float x, y, vx, vy; int life; uint16_t c; };
Part parts[100];
void clearParts() { for (auto& p : parts) p.life = 0; }
void boom(float x, float y, int n, uint16_t c1, uint16_t c2) {
for (auto& p : parts) {
if (n <= 0) break;
if (p.life > 0) continue;
float a = random(0, 628) / 100.0f;
float s = random(10, 50) / 10.0f;
p.x = x; p.y = y;
p.vx = cosf(a) * s; p.vy = sinf(a) * s;
p.life = random(20, 45);
p.c = random(2) ? c1 : c2;
n--;
}
}
void updateParts() {
for (auto& p : parts) {
if (p.life <= 0) continue;
p.x += p.vx; p.y += p.vy;
p.vx *= 0.95f; p.vy *= 0.95f;
p.life--;
}
}
void drawParts() {
for (auto& p : parts) {
if (p.life <= 0) continue;
int s = p.life > 25 ? 4 : (p.life > 10 ? 3 : 2);
fillR((int)p.x - s / 2, (int)p.y - s / 2, s, s, p.c);
}
}
enum Scene { SC_MENU, SC_FLAP, SC_UFO };
Scene scene = SC_MENU;
uint32_t stepCount = 0;
int best[GAMES] = { 0, 0 };
bool goOn = false, goNewBest = false;
uint32_t goStep = 0;
int goScore = 0;
void startScene(Scene s);
void showOver(int score) {
goOn = true;
goStep = stepCount;
goScore = score;
int i = scene - 1;
goNewBest = score > best[i] && score > 0;
if (score > best[i]) best[i] = score;
}
void drawBird(int x, int y, int frame) {
canvas.fillTriangle(x - 10, y - 2, x - 21, y - 9, x - 20, y + 5, rgb(20, 60, 140));
canvas.fillCircle(x, y, 14, rgb(20, 60, 140));
canvas.fillCircle(x, y, 12, rgb(70, 150, 255));
canvas.fillEllipse(x - 1, y + 5, 8, 5, rgb(245, 242, 225));
int wy = frame == 0 ? y - 4 : (frame == 1 ? y + 1 : y + 5);
canvas.fillEllipse(x - 5, wy, 7, 4, rgb(36, 96, 200));
canvas.fillCircle(x + 5, y - 5, 4, C_WHITE);
canvas.fillCircle(x + 6, y - 5, 2, C_BLACK);
canvas.fillTriangle(x + 10, y - 2, x + 21, y + 1, x + 10, y + 4, rgb(255, 170, 40));
}
void drawUfo(int x, int y, uint16_t col, int ph) {
canvas.fillEllipse(x, y - 5, 11, 9, rgb(140, 230, 255));
canvas.fillEllipse(x - 4, y - 8, 3, 2, C_WHITE);
canvas.fillEllipse(x, y + 2, 22, 7, darken(col));
canvas.fillEllipse(x, y + 1, 20, 5, col);
for (int i = -1; i <= 1; i++)
canvas.fillCircle(x + i * 11, y + 3, 2, ((ph + i + 2) & 1) ? rgb(255, 255, 120) : C_WHITE);
}
void drawShip(int x, int y) {
canvas.fillTriangle(x, y - 12, x - 22, y + 14, x + 22, y + 14, rgb(70, 90, 150));
canvas.fillTriangle(x, y - 12, x - 18, y + 12, x + 18, y + 12, rgb(110, 140, 210));
canvas.fillTriangle(x, y - 26, x - 9, y + 14, x + 9, y + 14, rgb(225, 230, 245));
canvas.fillEllipse(x, y - 4, 4, 8, rgb(60, 200, 255));
fillR(x - 23, y + 4, 3, 12, rgb(255, 70, 90));
fillR(x + 20, y + 4, 3, 12, rgb(255, 70, 90));
int fl = 8 + (stepCount % 4) * 3;
canvas.fillTriangle(x - 6, y + 15, x + 6, y + 15, x, y + 15 + fl, rgb(255, 140, 30));
canvas.fillTriangle(x - 3, y + 15, x + 3, y + 15, x, y + 15 + fl / 2, rgb(255, 240, 150));
}
void drawBack() {
canvas.fillCircle(24, 24, 15, rgb(20, 22, 34));
canvas.fillTriangle(17, 24, 28, 16, 28, 32, C_WHITE);
}
void drawOver() {
char b[24];
canvas.fillRoundRect(30, 140, 260, 250, 18, C_WHITE);
canvas.fillRoundRect(34, 144, 252, 242, 15, rgb(24, 26, 46));
textC(160, 162, "GAME OVER", 3, rgb(255, 96, 96));
textC(160, 202, "SCORE", 2, rgb(170, 170, 200));
snprintf(b, sizeof(b), "%d", goScore);
textC(160, 224, b, 4, C_WHITE);
snprintf(b, sizeof(b), "BEST %d", best[scene - 1]);
textC(160, 266, b, 2, rgb(255, 214, 80));
if (goNewBest && (stepCount / 15) % 2) textC(160, 290, "NEW BEST!", 2, rgb(120, 255, 140));
canvas.fillRoundRect(46, 324, 108, 46, 12, rgb(70, 200, 110));
textC(100, 339, "RETRY", 2, C_WHITE);
canvas.fillRoundRect(166, 324, 108, 46, 12, rgb(80, 120, 230));
textC(220, 339, "MENU", 2, C_WHITE);
}
int menuSel = 0;
const char* GAME_NAME[GAMES] = { "SKY HOP", "UFO STRIKE" };
const uint16_t CARD_BG[GAMES] = { rgb(70, 160, 230), rgb(18, 14, 44) };
int cardY(int i) { return 176 + i * 120; }
void menuStart() {
bgBegin();
gradient(0, SCR_H, rgb(18, 14, 46), rgb(70, 24, 80));
for (int i = 0; i < 80; i++)
fillR(random(SCR_W), random(SCR_H), 1 + random(2), 1, mix(rgb(110, 110, 160), C_WHITE, random(256)));
bgEnd();
}
void menuUpdate() {
if (in.tDown) {
for (int i = 0; i < GAMES; i++) {
if (hit(in.tx, in.ty, 16, cardY(i), 288, 96)) { menuSel = i; startScene((Scene)(i + 1)); return; }
}
}
}
void menuDraw() {
char b[20];
textCO(160, 34, "ARCADE", 6, rgb(255, 214, 60), rgb(140, 40, 20));
textC(160, 96, "CHOOSE YOUR GAME", 2, rgb(200, 190, 240));
for (int i = 0; i < GAMES; i++) {
int y = cardY(i);
bool s = (i == menuSel);
int bw = s ? 4 : 2;
uint16_t bc = s ? mix(rgb(255, 190, 30), rgb(255, 250, 190), (int)(128 + 120 * sinf(stepCount * 0.12f)))
: rgb(90, 80, 130);
canvas.fillRoundRect(16, y, 288, 96, 16, bc);
canvas.fillRoundRect(16 + bw, y + bw, 288 - bw * 2, 96 - bw * 2, 13, CARD_BG[i]);
if (i == 0) drawBird(64, y + 48, s ? (stepCount / 5) % 3 : 1);
if (i == 1) drawUfo(64, y + 46 + (s ? (int)(sinf(stepCount * 0.1f) * 3) : 0), rgb(255, 80, 200), stepCount / 8);
text(114, y + 24, GAME_NAME[i], 3, C_WHITE);
snprintf(b, sizeof(b), "BEST %d", best[i]);
text(114, y + 62, b, 2, rgb(255, 230, 120));
}
textC(160, 456, "TAP A GAME TO PLAY", 1, rgb(170, 160, 210));
}
#define GROUND_Y 420
#define PW 58
#define CAP_H 22
#define GAP 150
#define SPACING 190
#define BIRD_X 90
const float GRAV = 0.38f, FLAP_V = -6.6f, MAX_FALL = 9.0f;
const uint16_t C_PB = rgb(236, 124, 70), C_PD = rgb(124, 52, 30), C_PL = rgb(255, 184, 130);
const uint16_t C_GRASS = rgb(96, 196, 72), C_GRASS_D = rgb(60, 150, 50);
const uint16_t C_DIRT1 = rgb(230, 206, 140), C_DIRT2 = rgb(212, 182, 112);
struct Pillar { float x; int gy; bool scored; };
struct FlapState { float y, vy, scroll; int state, score; uint32_t t0; Pillar p[3]; } flap;
void cloud(int x, int y, int s) {
canvas.fillCircle(x, y, s, C_WHITE);
canvas.fillCircle(x + s, y - s / 2, s * 5 / 4, C_WHITE);
canvas.fillCircle(x + s * 5 / 2, y, s, C_WHITE);
fillR(x, y, s * 5 / 2, s, C_WHITE);
}
void flapStart() {
bgBegin();
gradient(0, GROUND_Y, rgb(78, 168, 240), rgb(204, 238, 252));
canvas.fillCircle(252, 96, 34, rgb(255, 236, 160));
canvas.fillCircle(252, 96, 26, rgb(255, 248, 210));
cloud(30, 120, 16);
cloud(170, 70, 12);
cloud(190, 200, 18);
cloud(50, 280, 12);
canvas.fillCircle(40, GROUND_Y + 40, 100, rgb(158, 216, 168));
canvas.fillCircle(200, GROUND_Y + 60, 120, rgb(158, 216, 168));
canvas.fillCircle(330, GROUND_Y + 30, 90, rgb(158, 216, 168));
canvas.fillCircle(110, GROUND_Y + 40, 70, rgb(118, 192, 128));
canvas.fillCircle(280, GROUND_Y + 50, 80, rgb(118, 192, 128));
fillR(0, GROUND_Y, SCR_W, SCR_H - GROUND_Y, C_DIRT2);
fillR(0, GROUND_Y, SCR_W, 6, C_GRASS);
fillR(0, GROUND_Y + 6, SCR_W, 2, C_GRASS_D);
for (int i = 0; i < 40; i++) fillR(random(SCR_W), random(GROUND_Y + 24, SCR_H), 3, 2, rgb(190, 160, 96));
bgEnd();
flap.y = 220; flap.vy = 0; flap.scroll = 0;
flap.state = 0; flap.score = 0;
for (int i = 0; i < 3; i++) {
flap.p[i].x = SCR_W + 40 + i * SPACING;
flap.p[i].gy = random(140, 291);
flap.p[i].scored = false;
}
}
void flapDie() {
if (flap.state != 1) return;
flap.state = 2;
flap.t0 = stepCount;
flap.vy = -3;
boom(BIRD_X, flap.y, 16, C_WHITE, rgb(255, 170, 40));
}
void flapUpdate() {
float speed = 2.6f + fminf(flap.score * 0.04f, 1.2f);
bool tap = in.tDown;
if (flap.state == 0) {
flap.y = 220 + sinf(stepCount * 0.08f) * 8;
flap.scroll += speed;
if (tap) { flap.state = 1; flap.vy = FLAP_V; }
} else if (flap.state == 1) {
if (tap) flap.vy = FLAP_V;
flap.vy += GRAV;
if (flap.vy > MAX_FALL) flap.vy = MAX_FALL;
flap.y += flap.vy;
if (flap.y < 14) { flap.y = 14; flap.vy = 0; }
flap.scroll += speed;
for (auto& p : flap.p) {
p.x -= speed;
if (p.x < -PW - 10) { p.x += 3 * SPACING; p.gy = random(140, 291); p.scored = false; }
if (!p.scored && p.x + PW < BIRD_X) { p.scored = true; flap.score++; }
int top = p.gy - GAP / 2, bot = p.gy + GAP / 2;
if (BIRD_X + 11 > p.x - 5 && BIRD_X - 11 < p.x + PW + 5 && (flap.y - 11 < top || flap.y + 11 > bot)) flapDie();
}
if (flap.y + 13 >= GROUND_Y) { flap.y = GROUND_Y - 13; flapDie(); }
} else {
flap.vy += GRAV;
if (flap.vy > MAX_FALL) flap.vy = MAX_FALL;
flap.y += flap.vy;
if (flap.y > GROUND_Y - 13) flap.y = GROUND_Y - 13;
if (!goOn && stepCount - flap.t0 > 45) showOver(flap.score);
}
}
void pillarBody(int x, int y0, int y1) {
int h = y1 - y0;
if (h <= 0) return;
fillR(x, y0, PW, h, C_PB);
fillR(x, y0, 3, h, C_PD);
fillR(x + 3, y0, 7, h, C_PL);
fillR(x + PW - 3, y0, 3, h, C_PD);
}
void pillarCap(int x, int y) {
fillR(x - 5, y, PW + 10, CAP_H, C_PD);
fillR(x - 2, y + 3, PW + 4, CAP_H - 6, C_PB);
fillR(x - 2, y + 3, 8, CAP_H - 6, C_PL);
}
void drawPillar(int x, int top, int bot) {
pillarBody(x, 0, top - CAP_H);
pillarCap(x, top - CAP_H);
pillarBody(x, bot + CAP_H, GROUND_Y);
pillarCap(x, bot);
}
void flapDraw() {
char b[12];
for (auto& p : flap.p) drawPillar((int)p.x, p.gy - GAP / 2, p.gy + GAP / 2);
int s = (int)flap.scroll;
for (int j = 0; j < 12; j++) {
uint16_t* row = canvas.buf + (GROUND_Y + 8 + j) * SCR_W;
for (int x = 0; x < SCR_W; x++) row[x] = (((x + j + s) % 24) < 12) ? C_DIRT1 : C_DIRT2;
}
int wing = flap.state == 2 ? 1 : (stepCount / 5) % 3;
drawBird(BIRD_X, (int)flap.y, wing);
if (flap.state == 0) {
textCO(160, 130, "GET READY", 4, C_WHITE, rgb(30, 60, 110));
textCO(160, 300, "TAP TO FLY", 2, C_WHITE, rgb(30, 60, 110));
} else {
snprintf(b, sizeof(b), "%d", flap.score);
textCO(160, 50, b, 6, C_WHITE, rgb(40, 50, 70));
}
}
#define SHIP_Y 422
const uint16_t UFO_COLS[4] = { rgb(255, 80, 200), rgb(120, 255, 90), rgb(255, 160, 40), rgb(80, 220, 255) };
struct Shot { bool on; float x, y; };
struct Ufo { bool on; float x, y, bx, ph, vy; uint16_t col; };
struct Star { float x, y, v; uint8_t sz; uint16_t c; };
struct SpaceState {
float sx, tx;
int lives, score, state, fireT, spawnT, inv;
uint32_t t0, start;
Shot b[24];
Shot eb[16];
Ufo u[12];
Star st[60];
} space;
void nebula(int x, int y, int r, uint16_t c) {
uint16_t base = rgb(10, 6, 26);
canvas.fillCircle(x, y, r, mix(base, c, 90));
canvas.fillCircle(x + r / 4, y - r / 5, r * 3 / 4, mix(base, c, 150));
canvas.fillCircle(x - r / 6, y + r / 6, r / 2, mix(base, c, 210));
}
void planet(int cx, int cy, int r) {
for (int y = -r; y <= r; y++) {
for (int x = -r; x <= r; x++) {
if (x * x + y * y > r * r) continue;
int dx = x + r / 3, dy = y + r / 3;
bool lit = dx * dx + dy * dy < r * r;
uint16_t c = lit ? rgb(236, 146, 86) : rgb(120, 60, 48);
if (((y + r) / 9) % 2 == 0) c = mix(c, C_BLACK, 40);
int px = cx + x, py = cy + y;
if (px >= 0 && px < SCR_W && py >= 0 && py < SCR_H) canvas.buf[py * SCR_W + px] = c;
}
}
}
void ufoStart() {
bgBegin();
gradient(0, SCR_H, rgb(4, 4, 18), rgb(16, 6, 34));
nebula(80, 180, 90, rgb(120, 40, 170));
nebula(250, 330, 80, rgb(30, 130, 150));
for (int i = 0; i < 80; i++)
fillR(random(SCR_W), random(SCR_H), 1, 1, mix(rgb(60, 60, 90), rgb(160, 160, 200), random(256)));
planet(262, 110, 44);
bgEnd();
space.sx = space.tx = 160;
space.lives = 3;
space.score = 0;
space.state = 0;
space.fireT = 0;
space.spawnT = 40;
space.inv = 0;
space.start = stepCount;
for (auto& b : space.b) b.on = false;
for (auto& e : space.eb) e.on = false;
for (auto& u : space.u) u.on = false;
for (int i = 0; i < 60; i++) {
Star& s = space.st[i];
int layer = i % 3;
s.x = random(SCR_W);
s.y = random(SCR_H);
s.v = layer == 0 ? 0.6f : (layer == 1 ? 1.3f : 2.4f);
s.sz = layer == 2 ? 2 : 1;
s.c = layer == 0 ? rgb(120, 120, 150) : (layer == 1 ? rgb(190, 190, 220) : C_WHITE);
}
}
void fireShot(float x) {
for (auto& b : space.b) {
if (b.on) continue;
b.on = true; b.x = x; b.y = SHIP_Y - 26;
return;
}
}
void enemyShot(float x, float y) {
for (auto& e : space.eb) {
if (e.on) continue;
e.on = true; e.x = x; e.y = y;
return;
}
}
void spawnUfo() {
for (auto& u : space.u) {
if (u.on) continue;
u.on = true;
u.bx = random(60, 261);
u.y = -20;
u.ph = random(0, 628) / 100.0f;
u.vy = 0.7f + fminf(space.score / 400.0f, 1.2f);
u.x = u.bx + sinf(u.ph) * 40;
u.col = UFO_COLS[random(4)];
return;
}
}
void shipHit() {
if (space.inv > 0 || space.state != 0) return;
space.lives--;
boom(space.sx, SHIP_Y, 30, rgb(255, 140, 30), rgb(255, 230, 80));
if (space.lives <= 0) {
space.state = 1;
space.t0 = stepCount;
} else {
space.inv = 100;
}
}
void ufoUpdate() {
for (auto& s : space.st) {
s.y += s.v;
if (s.y > SCR_H) { s.y = 0; s.x = random(SCR_W); }
}
if (space.state == 0) {
if (in.tHeld && in.ty > 60) space.tx = in.tx;
space.tx = constrain(space.tx, 22, SCR_W - 22);
space.sx += (space.tx - space.sx) * 0.3f;
if (space.inv > 0) space.inv--;
if (--space.fireT <= 0) {
space.fireT = 11;
if (space.score >= 300) { fireShot(space.sx - 10); fireShot(space.sx + 10); }
else fireShot(space.sx);
}
if (--space.spawnT <= 0) {
spawnUfo();
int t = 75 - space.score / 15;
space.spawnT = t < 22 ? 22 : t;
}
}
for (auto& b : space.b) {
if (!b.on) continue;
b.y -= 10;
if (b.y < -14) b.on = false;
}
for (auto& e : space.eb) {
if (!e.on) continue;
e.y += 4.2f;
if (e.y > SCR_H + 6) e.on = false;
}
int fireChance = 3 + (space.score / 50 > 9 ? 9 : space.score / 50);
for (auto& u : space.u) {
if (!u.on) continue;
u.ph += 0.04f;
u.y += u.vy;
u.x = u.bx + sinf(u.ph) * 40;
if (u.y > SCR_H + 20) u.on = false;
if (space.state == 0 && u.y > 0 && u.y < SHIP_Y - 60 && random(1000) < fireChance) enemyShot(u.x, u.y + 8);
}
for (auto& b : space.b) {
if (!b.on) continue;
for (auto& u : space.u) {
if (!u.on) continue;
if (fabsf(b.x - u.x) < 21 && fabsf(b.y - u.y) < 12) {
b.on = false;
u.on = false;
boom(u.x, u.y, 18, u.col, C_WHITE);
space.score += 10;
break;
}
}
}
if (space.state == 0 && space.inv == 0) {
for (auto& u : space.u) {
if (!u.on) continue;
if (fabsf(u.x - space.sx) < 30 && fabsf(u.y - SHIP_Y) < 24) {
u.on = false;
boom(u.x, u.y, 18, u.col, C_WHITE);
shipHit();
}
}
for (auto& e : space.eb) {
if (!e.on) continue;
if (fabsf(e.x - space.sx) < 14 && fabsf(e.y - SHIP_Y) < 18) {
e.on = false;
shipHit();
}
}
}
if (space.state == 1 && !goOn && stepCount - space.t0 > 70) showOver(space.score);
}
void ufoDraw() {
char b[12];
for (auto& s : space.st) fillR((int)s.x, (int)s.y, s.sz, s.sz, s.c);
for (auto& e : space.eb) {
if (!e.on) continue;
canvas.fillCircle((int)e.x, (int)e.y, 4, rgb(255, 90, 40));
canvas.fillCircle((int)e.x, (int)e.y, 2, rgb(255, 230, 120));
}
for (auto& sh : space.b) {
if (!sh.on) continue;
fillR((int)sh.x - 2, (int)sh.y, 4, 14, rgb(60, 220, 255));
fillR((int)sh.x - 1, (int)sh.y + 1, 2, 12, C_WHITE);
}
for (auto& u : space.u)
if (u.on) drawUfo((int)u.x, (int)u.y, u.col, stepCount / 8);
if (space.state == 0 && (space.inv == 0 || (space.inv / 5) % 2 == 0)) drawShip((int)space.sx, SHIP_Y);
snprintf(b, sizeof(b), "%d", space.score);
textCO(160, 14, b, 3, C_WHITE, rgb(40, 20, 70));
for (int i = 0; i < space.lives; i++) {
int x = 300 - i * 22, y = 24;
canvas.fillTriangle(x, y - 8, x - 7, y + 7, x + 7, y + 7, rgb(80, 220, 255));
}
if (stepCount - space.start < 70) textCO(160, 200, "GO!", 7, rgb(120, 255, 200), rgb(20, 60, 80));
}
void startScene(Scene s) {
scene = s;
goOn = false;
clearParts();
switch (s) {
case SC_MENU: menuStart(); break;
case SC_FLAP: flapStart(); break;
case SC_UFO: ufoStart(); break;
}
}
void update() {
stepCount++;
if (scene != SC_MENU) {
if (in.tDown && in.tx < 48 && in.ty < 48) { startScene(SC_MENU); return; }
if (goOn) {
if (stepCount - goStep > 30) {
if (in.tDown && hit(in.tx, in.ty, 166, 324, 108, 46)) { startScene(SC_MENU); return; }
if (in.tDown && hit(in.tx, in.ty, 46, 324, 108, 46)) { startScene(scene); return; }
}
in.tDown = false; in.tHeld = false;
}
}
switch (scene) {
case SC_MENU: menuUpdate(); break;
case SC_FLAP: flapUpdate(); break;
case SC_UFO: ufoUpdate(); break;
}
updateParts();
}
void render() {
memcpy(canvas.buf, bgBuf, SCR_W * SCR_H * 2);
switch (scene) {
case SC_MENU: menuDraw(); break;
case SC_FLAP: flapDraw(); break;
case SC_UFO: ufoDraw(); break;
}
drawParts();
if (scene != SC_MENU) drawBack();
if (goOn) drawOver();
}
uint32_t lastT = 0, acc = 0;
void setup() {
Serial.begin(115200);
delay(1500);
Serial.println("1. Arranque");
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
SDRAM.begin();
Serial.println("2. SDRAM iniciada");
const size_t FB_SIZE = SCR_W * SCR_H * 2;
scrBuf = (uint16_t*)SDRAM.malloc(FB_SIZE);
canvas.buf = (uint16_t*)SDRAM.malloc(FB_SIZE);
bgBuf = (uint16_t*)SDRAM.malloc(FB_SIZE);
if (!scrBuf || !canvas.buf || !bgBuf) {
Serial.println("Sin memoria SDRAM");
while (true) delay(1000);
}
memset(scrBuf, 0, FB_SIZE);
memset(canvas.buf, 0, FB_SIZE);
memset(bgBuf, 0, FB_SIZE);
canvas.setTextWrap(false);
Serial.println("3. Buffers listos");
tft->begin(SPI_HZ);
tft->fillScreen(C_BLACK);
Serial.println("4. Pantalla iniciada");
pinMode(CTP_RST, OUTPUT);
digitalWrite(CTP_RST, LOW);
delay(10);
digitalWrite(CTP_RST, HIGH);
delay(300);
pinMode(CTP_INT, INPUT);
Wire.begin();
Wire.setClock(400000);
Serial.println("5. Tactil iniciado");
randomSeed(micros());
startScene(SC_MENU);
lastT = millis();
Serial.println("6. Menu en marcha");
}
void loop() {
readInput();
uint32_t now = millis();
acc += now - lastT;
lastT = now;
if (acc > 100) acc = 100;
bool stepped = false;
while (acc >= STEP_MS) {
update();
in.tDown = false;
acc -= STEP_MS;
stepped = true;
}
if (stepped) {
render();
present();
}
}
Vinyl Player
A record player for your iPhone, on the same 4" touchscreen. Whatever you play shows up as a spinning record, with its album art and a background that matches it. Tap the button to play or pause.
Components 04
- 01
Arduino GIGA R1 WiFi
Gets the song over Bluetooth and the album art over Wi-Fi
- 02
4" touchscreen
480 × 320, ST7796S display with FT6336U touch
- 03
Breadboard
To wire it all up without soldering
- 04
Jumper wires
Same wiring as the Arduino Console
Libraries
- GFX Library for Arduino
- ArduinoBLE
- JPEGDEC
Setup 03
- 01
Save the four files
All in one folder named vinyl_player
- 02
Add your settings
Rename arduino_secrets.example.h to arduino_secrets.h and fill it in
- 03
Pair your iPhone
Settings › Bluetooth › Vinyl, then confirm the code on both screens
// =====================================================================
// Vinyl Player for the Arduino GIGA R1 WiFi
//
// Shows the song playing on an iPhone as a spinning record with its album
// art, on a 4" ST7796S display (480x320, SPI1) with an FT6336U touch panel.
// Track info and play/pause use Bluetooth LE (Apple Media Service, AMS).
// Album art is fetched over Wi-Fi from the iTunes Search API (HTTPS with
// certificate verification).
//
// Security and privacy:
// - LE Secure Connections pairing with numeric comparison: the code is
// shown on the display and must be confirmed on both devices.
// - Only one iPhone can be paired; the board refuses any other. Connections
// that are not encrypted within a few seconds are dropped.
// - Pairing keys are stored in internal flash, encrypted with AES-256-GCM.
// Nothing sensitive is printed to the serial port.
// - Only the artist and album (or title) leave the board, to look up the
// cover. No location, country or device identifiers are sent.
// - To forget the paired iPhone, hold the play button for 5 seconds.
//
// Setup: copy arduino_secrets.example.h to arduino_secrets.h and fill it in.
// Built with Arduino Mbed OS GIGA Boards 4.6.0, ArduinoBLE 2.1.0,
// GFX Library for Arduino 1.6.9 and JPEGDEC 1.8.4.
// =====================================================================
#include "mbed.h"
#include <SPI.h>
#include <Wire.h>
#include <WiFi.h>
#include <WiFiSSLClient.h>
#include <SDRAM.h>
#include <ArduinoBLE.h>
#include <utility/HCI.h>
#include <utility/ATT.h>
#include <utility/L2CAPSignaling.h>
#include <Arduino_GFX_Library.h>
#include <JPEGDEC.h>
#include "mbedtls/gcm.h"
#include "mbedtls/sha256.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/ecp.h"
#include "mbedtls/ecdh.h"
#include <utility/btct.h>
#include "hal/trng_api.h"
#include "arduino_secrets.h"
#include "vinyl_fonts.h"
using namespace std::chrono_literals;
// ---------------- Pins ----------------
#define TFT_CS 10
#define TFT_DC 6
#define TFT_RST 7
#define TFT_BL 5
#define CTP_RST 4
#define CTP_ADDR 0x38
// ---------------- Display ----------------
// 1 and 3 are both landscape: if the image is upside down, use the other one.
// Touch coordinates follow the selected rotation.
#define TFT_ROTATION 1
#define TFT_IPS false
#define SPI_HZ 24000000
#define SCR_W 480
#define SCR_H 320
// ---------------- Bluetooth: Apple Media Service ----------------
#define DEVICE_NAME "Vinyl"
#define AMS_SERVICE "89d3502b-0f36-433a-8ef4-c502ad55f8dc"
#define AMS_REMOTE "9b3c81d8-57b1-4a8a-b8df-0e56f7ca51c2"
#define AMS_UPDATE "2f7cabce-808d-411f-9a0c-bb92ba96c102"
#define AMS_ENTITY_PLAYER 0
#define AMS_ENTITY_TRACK 2
#define AMS_PLAYER_PLAYBACKINFO 1
#define AMS_TRACK_ARTIST 0
#define AMS_TRACK_ALBUM 1
#define AMS_TRACK_TITLE 2
#define AMS_TRACK_DURATION 3
#define AMS_CMD_TOGGLE 2
// ---------------- Timing ----------------
#define ENCRYPT_TIMEOUT_MS 12000 // a bonded link that does not encrypt in time is dropped
#define PAIRING_TIMEOUT_MS 45000 // time allowed to pair a new iPhone
#define FORGET_HOLD_MS 5000 // hold the play button this long to forget the iPhone
#define ARM_ANIM_MS 750
#define FRAME_MS 16
// =====================================================================
// Types
// =====================================================================
struct Rect { int16_t x0, y0, x1, y1; }; // x1 and y1 are exclusive
struct BondRecord {
uint8_t addr[6];
uint8_t irk[16];
uint8_t ltk[16];
};
struct ArtRequest {
char artist[96];
char album[96];
char title[96];
uint32_t seq;
};
// =====================================================================
// Arduino_GFX data bus on the GIGA's SPI1
// =====================================================================
class GigaSPI1Bus : public Arduino_DataBus {
public:
GigaSPI1Bus(int8_t dc, int8_t cs) : _dc(dc), _cs(cs) {}
bool begin(int32_t speed = GFX_NOT_DEFINED, int8_t dataMode = GFX_NOT_DEFINED) override {
(void)dataMode;
_speed = (speed == GFX_NOT_DEFINED) ? 24000000 : speed;
::pinMode(_dc, OUTPUT);
::digitalWrite(_dc, HIGH);
::pinMode(_cs, OUTPUT);
::digitalWrite(_cs, HIGH);
SPI1.begin();
return true;
}
void beginWrite() override {
SPI1.beginTransaction(SPISettings(_speed, MSBFIRST, SPI_MODE0));
::digitalWrite(_dc, HIGH);
::digitalWrite(_cs, LOW);
}
void endWrite() override {
::digitalWrite(_cs, HIGH);
SPI1.endTransaction();
}
void writeCommand(uint8_t c) override {
::digitalWrite(_dc, LOW);
SPI1.transfer(c);
::digitalWrite(_dc, HIGH);
}
void writeCommand16(uint16_t c) override {
::digitalWrite(_dc, LOW);
_buf[0] = c >> 8;
_buf[1] = c & 0xFF;
SPI1.transfer(_buf, 2);
::digitalWrite(_dc, HIGH);
}
void writeCommandBytes(uint8_t* data, uint32_t len) override {
::digitalWrite(_dc, LOW);
sendBytes(data, len);
::digitalWrite(_dc, HIGH);
}
void write(uint8_t d) override { SPI1.transfer(d); }
void write16(uint16_t d) override {
_buf[0] = d >> 8;
_buf[1] = d & 0xFF;
SPI1.transfer(_buf, 2);
}
void writeRepeat(uint16_t p, uint32_t len) override {
while (len) {
uint32_t n = len > BUF_PIX ? BUF_PIX : len;
for (uint32_t i = 0; i < n; i++) {
_buf[i * 2] = p >> 8;
_buf[i * 2 + 1] = p & 0xFF;
}
SPI1.transfer(_buf, n * 2);
len -= n;
}
}
void writePixels(uint16_t* data, uint32_t len) override {
while (len) {
uint32_t n = len > BUF_PIX ? BUF_PIX : len;
for (uint32_t i = 0; i < n; i++) {
_buf[i * 2] = data[i] >> 8;
_buf[i * 2 + 1] = data[i] & 0xFF;
}
SPI1.transfer(_buf, n * 2);
data += n;
len -= n;
}
}
void writeBytes(uint8_t* data, uint32_t len) override { sendBytes(data, len); }
void writeIndexedPixels(uint8_t* data, uint16_t* idx, uint32_t len) override {
while (len--) write16(idx[*data++]);
}
void writeIndexedPixelsDouble(uint8_t* data, uint16_t* idx, uint32_t len) override {
while (len--) {
uint16_t p = idx[*data++];
write16(p);
write16(p);
}
}
private:
static const uint32_t BUF_PIX = 512;
int8_t _dc, _cs;
int32_t _speed = 24000000;
uint8_t _buf[BUF_PIX * 2];
void sendBytes(uint8_t* data, uint32_t len) {
while (len) {
uint32_t n = len > sizeof(_buf) ? sizeof(_buf) : len;
memcpy(_buf, data, n);
SPI1.transfer(_buf, n);
data += n;
len -= n;
}
}
};
GigaSPI1Bus* bus = new GigaSPI1Bus(TFT_DC, TFT_CS);
Arduino_TFT* tft = new Arduino_ST7796(bus, TFT_RST, TFT_ROTATION, TFT_IPS);
// =====================================================================
// Global state
// =====================================================================
uint16_t* fb = nullptr; // frame buffer sent to the display
uint16_t* bgL = nullptr; // static layer: background, cover, text and record
uint8_t* bg8 = nullptr; // 8-bit-per-channel scratch layer for gradients and shadows
// Layout
const float DISC_CX = 340.0f, DISC_CY = 120.0f, DISC_R = 105.0f;
const float LABEL_R = 35.0f, HOLE_R = 4.0f;
const float ARM_PX = 450.0f, ARM_PY = 28.0f;
const float BTN_CX = 50.0f, BTN_CY = 276.0f, BTN_R = 26.0f;
const int BAR_X = 96, BAR_Y = 273, BAR_W = 360, BAR_H = 6;
const int COVER_X = 24, COVER_Y = 24, COVER_S = 156;
const int TITLE_X = 24, TITLE_TOP = 194, TEXT_MAXW = 184;
const float ARM_UP = 4.0f, ARM_DOWN = 22.0f, ARM_WOBBLE = 1.5f;
const float DISC_DPS = 200.0f; // 33 1/3 rpm
// Background color, derived from the cover
uint8_t bgR = 28, bgG = 28, bgB = 30;
// Album art
uint16_t* coverBuf = nullptr; // 156x156
bool coverValid = false;
// On-screen text
char uiTitle[128] = "Not connected";
char uiArtist[128] = "Settings \xE2\x80\xBA Bluetooth \xE2\x80\xBA " DEVICE_NAME;
// Track and player state from AMS
char amsTitle[128] = "", amsArtist[128] = "", amsAlbum[128] = "";
float amsDuration = 0.0f, amsElapsed = 0.0f, amsRate = 0.0f;
uint8_t amsState = 0;
uint32_t amsElapsedAt = 0;
bool amsTrackDirty = false;
uint32_t amsTrackChangedAt = 0;
char lastArtKey[200] = "";
char lastArtKeyTried[200] = "";
// Animation
float armDeg = ARM_UP, armFrom = ARM_UP, armTo = ARM_UP;
uint32_t armAnimStart = 0;
bool armAnimating = false;
bool spinning = false;
uint32_t wobbleStart = 0;
float discDeg = 0.0f;
uint32_t lastFrame = 0;
// Last drawn state, so only what changed is redrawn
float drawnArmDeg = -999.0f, drawnDiscDeg = -999.0f, drawnBarW = -1.0f;
int drawnIcon = -1;
Rect drawnArmRect = {0, 0, 0, 0};
bool needFull = true;
bool needBgRebuild = true;
// Connection
BLEDevice central;
bool wasConnected = false;
uint32_t connectedAt = 0;
bool secReqSent = false;
bool amsReady = false;
int discoverTries = 0;
uint32_t lastDiscoverTry = 0;
BLECharacteristic* chRemote = nullptr; // pointers, so ArduinoBLE's reference counting stays balanced
BLECharacteristic* chUpdate = nullptr;
// Pairing
BondRecord bond;
bool hasBond = false;
BondRecord pendingBond;
bool bondDirty = false;
bool storageUsable = false;
uint32_t storageAddr = 0, storageSize = 0;
uint32_t pairCode = 0;
volatile bool pairingShown = false;
mbed::FlashIAP flash;
// Touch
bool touchWasDown = false;
uint32_t touchDownAt = 0;
bool touchOnButton = false;
bool forgetDone = false;
// BLE is polled often so no packets from the iPhone are lost
volatile bool inPairingUi = false;
bool pollBusy = false;
// Pairing: the P-256 key exchange runs on the board (see smpWatch)
bool smpDone = false;
uint32_t smpReqSeenAt = 0;
uint8_t smpLastFlags = 0;
uint8_t smpLocalPk[64];
// Flash writes run on the network thread so they never stall Bluetooth
volatile int storageOp = 0; // 0 idle, 1 save, 2 erase; guarded by netMtx
BondRecord storageRec;
// Network thread: Wi-Fi, HTTPS and JPEG decoding, so the animation never waits.
// It must run at the same priority as loop(), so RTX round-robins the two every
// 5 ms. A lower priority starves it: loop() never blocks, and the core's socket
// reader thread (also osPriorityNormal) busy-waits until its buffer is drained.
rtos::Thread netThread(osPriorityNormal, 32 * 1024);
rtos::Mutex netMtx;
ArtRequest netReq;
volatile bool netReqPending = false;
volatile bool netResultReady = false;
volatile uint32_t netResultSeq = 0;
volatile bool netResultOk = false;
uint32_t artSeq = 0;
int artRetries = 0;
uint32_t artRetryAt = 0;
uint16_t* netCover = nullptr; // 156x156
uint8_t netAvgR = 0, netAvgG = 0, netAvgB = 0;
uint8_t* jpgBuf = nullptr;
uint16_t* decBuf = nullptr;
const size_t JPG_MAX = 160 * 1024;
const int DEC_MAX = 400;
int decW = 0, decH = 0;
JPEGDEC jpeg;
char jsonBuf[12 * 1024];
// Advertising data: Flags + 128-bit service solicitation for AMS.
// ArduinoBLE keeps a pointer to it, so it must be static.
static const uint8_t ADV_RAW[] = {
0x02, 0x01, 0x06,
0x11, 0x15,
0xDC, 0xF8, 0x55, 0xAD, 0x02, 0xC5, 0xF4, 0x8E,
0x3A, 0x43, 0x36, 0x0F, 0x2B, 0x50, 0xD3, 0x89
};
BLEAdvertisingData advData;
BLEAdvertisingData scanData;
// =====================================================================
// Color and geometry helpers
// =====================================================================
static inline uint16_t rgb565(int r, int g, int b) {
if (r < 0) r = 0; if (r > 255) r = 255;
if (g < 0) g = 0; if (g > 255) g = 255;
if (b < 0) b = 0; if (b > 255) b = 255;
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
static inline uint16_t blend565(uint16_t dst, uint16_t src, float a) {
if (a <= 0.0f) return dst;
if (a >= 1.0f) return src;
int ia = (int)(a * 256.0f + 0.5f);
int dr = dst >> 11, dg = (dst >> 5) & 63, db = dst & 31;
int sr = src >> 11, sg = (src >> 5) & 63, sb = src & 31;
int tr = (sr - dr) * ia, tg = (sg - dg) * ia, tb = (sb - db) * ia;
dr += (tr + (tr >= 0 ? 128 : -128)) / 256; // symmetric rounding, so blends don't drift darker
dg += (tg + (tg >= 0 ? 128 : -128)) / 256;
db += (tb + (tb >= 0 ? 128 : -128)) / 256;
return (dr << 11) | (dg << 5) | db;
}
static inline float clampf(float v, float a, float b) { return v < a ? a : (v > b ? b : v); }
static inline float cov(float d) { return clampf(0.5f - d, 0.0f, 1.0f); }
static inline float shadowA(float d, float sigma) { return 0.5f * erfcf(d / (sigma * 1.41421356f)); }
static inline float sdRoundBox(float px, float py, float cx, float cy, float hw, float hh, float r) {
float qx = fabsf(px - cx) - (hw - r);
float qy = fabsf(py - cy) - (hh - r);
float ox = qx > 0 ? qx : 0, oy = qy > 0 ? qy : 0;
float inside = qx > qy ? qx : qy;
if (inside > 0) inside = 0;
return sqrtf(ox * ox + oy * oy) + inside - r;
}
static float sdTriangle(float px, float py, float x0, float y0, float x1, float y1, float x2, float y2) {
float e0x = x1 - x0, e0y = y1 - y0, e1x = x2 - x1, e1y = y2 - y1, e2x = x0 - x2, e2y = y0 - y2;
float v0x = px - x0, v0y = py - y0, v1x = px - x1, v1y = py - y1, v2x = px - x2, v2y = py - y2;
float t0 = clampf((v0x * e0x + v0y * e0y) / (e0x * e0x + e0y * e0y), 0, 1);
float t1 = clampf((v1x * e1x + v1y * e1y) / (e1x * e1x + e1y * e1y), 0, 1);
float t2 = clampf((v2x * e2x + v2y * e2y) / (e2x * e2x + e2y * e2y), 0, 1);
float p0x = v0x - e0x * t0, p0y = v0y - e0y * t0;
float p1x = v1x - e1x * t1, p1y = v1y - e1y * t1;
float p2x = v2x - e2x * t2, p2y = v2y - e2y * t2;
float s = (e0x * e2y - e0y * e2x) > 0 ? 1.0f : -1.0f;
float d0 = p0x * p0x + p0y * p0y, d1 = p1x * p1x + p1y * p1y, d2 = p2x * p2x + p2y * p2y;
float c0 = s * (v0x * e0y - v0y * e0x), c1 = s * (v1x * e1y - v1y * e1x), c2 = s * (v2x * e2y - v2y * e2x);
float d = d0 < d1 ? d0 : d1; if (d2 < d) d = d2;
float c = c0 < c1 ? c0 : c1; if (c2 < c) c = c2;
return -sqrtf(d) * (c > 0 ? 1.0f : -1.0f);
}
static Rect rectUnion(Rect a, Rect b) {
if (a.x1 <= a.x0 || a.y1 <= a.y0) return b;
if (b.x1 <= b.x0 || b.y1 <= b.y0) return a;
Rect r = { (int16_t)min(a.x0, b.x0), (int16_t)min(a.y0, b.y0), (int16_t)max(a.x1, b.x1), (int16_t)max(a.y1, b.y1) };
return r;
}
static Rect rectClip(Rect r) {
if (r.x0 < 0) r.x0 = 0;
if (r.y0 < 0) r.y0 = 0;
if (r.x1 > SCR_W) r.x1 = SCR_W;
if (r.y1 > SCR_H) r.y1 = SCR_H;
return r;
}
static bool rectEmpty(Rect r) { return r.x1 <= r.x0 || r.y1 <= r.y0; }
static Rect rectIntersect(Rect a, Rect b) {
Rect r = { (int16_t)max(a.x0, b.x0), (int16_t)max(a.y0, b.y0), (int16_t)min(a.x1, b.x1), (int16_t)min(a.y1, b.y1) };
return r;
}
// Standard easing, cubic-bezier(0.4, 0, 0.2, 1)
static float easeStandard(float t) {
const float x1 = 0.4f, y1 = 0.0f, x2 = 0.2f, y2 = 1.0f;
if (t <= 0) return 0;
if (t >= 1) return 1;
float lo = 0, hi = 1, s = t;
for (int i = 0; i < 24; i++) {
s = (lo + hi) * 0.5f;
float u = 1 - s;
float x = 3 * u * u * s * x1 + 3 * u * s * s * x2 + s * s * s;
if (x < t) lo = s; else hi = s;
}
float u = 1 - s;
return 3 * u * u * s * y1 + 3 * u * s * s * y2 + s * s * s;
}
// =====================================================================
// Text (anti-aliased Inter, UTF-8)
// =====================================================================
static uint32_t utf8Next(const char*& s) {
const uint8_t* p = (const uint8_t*)s;
uint32_t c = p[0];
if (c < 0x80) { s += 1; return c; }
if ((c & 0xE0) == 0xC0 && (p[1] & 0xC0) == 0x80) { s += 2; return ((c & 0x1F) << 6) | (p[1] & 0x3F); }
if ((c & 0xF0) == 0xE0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) {
s += 3; return ((c & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
}
if ((c & 0xF8) == 0xF0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) {
s += 4; return 0xFFFD;
}
s += 1;
return '?';
}
static const VGlyph* findGlyph(const VFont& f, uint32_t cp) {
int lo = 0, hi = f.n - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (f.g[mid].cp == cp) return &f.g[mid];
if (f.g[mid].cp < cp) lo = mid + 1; else hi = mid - 1;
}
if (cp != '?') return findGlyph(f, '?');
return &f.g[0];
}
static int textWidth16(const VFont& f, const char* s, int maxBytes) {
int w = 0;
const char* start = s;
while (*s && (s - start) < maxBytes) w += findGlyph(f, utf8Next(s))->adv16;
return w;
}
static void drawGlyphs(uint16_t* buf, const VFont& f, int x, int baseline, const char* s, int nBytes,
uint16_t color, float alpha, Rect clip) {
int pen16 = x * 16;
const char* start = s;
while (*s && (s - start) < nBytes) {
const VGlyph* g = findGlyph(f, utf8Next(s));
int gx = ((pen16 + 8) >> 4) + g->xo;
int gy = baseline + g->yo;
int stride = (g->w + 1) / 2;
for (int j = 0; j < g->h; j++) {
int yy = gy + j;
if (yy < clip.y0 || yy >= clip.y1) continue;
const uint8_t* row = f.bmp + g->off + j * stride;
for (int i = 0; i < g->w; i++) {
int xx = gx + i;
if (xx < clip.x0 || xx >= clip.x1) continue;
uint8_t v = (i & 1) ? (row[i >> 1] & 0x0F) : (row[i >> 1] >> 4);
if (!v) continue;
uint16_t* p = &buf[yy * SCR_W + xx];
*p = blend565(*p, color, alpha * (v / 15.0f));
}
}
pen16 += g->adv16;
}
}
// Draws text, cut with "…" if wider than maxW. align: 0 left, 1 centered
static void drawText(uint16_t* buf, const VFont& f, int x, int baseline, const char* s,
uint16_t color, float alpha, int maxW, int align, Rect clip) {
const char* ell = "\xE2\x80\xA6";
int len = strlen(s);
int w = textWidth16(f, s, 100000);
int nBytes = len;
bool cut = false;
if (maxW > 0 && w > maxW * 16) {
int ellW = textWidth16(f, ell, 100000);
int acc = 0;
const char* p = s;
const char* lastFit = s;
while (*p) {
const char* before = p;
int adv = findGlyph(f, utf8Next(p))->adv16;
if (acc + adv + ellW > maxW * 16) { lastFit = before; break; }
acc += adv;
lastFit = p;
}
while (lastFit > s && *(lastFit - 1) == ' ') lastFit--;
nBytes = lastFit - s;
w = acc + ellW;
cut = true;
}
int x0 = align == 1 ? x - (w + 8) / 32 : x;
drawGlyphs(buf, f, x0, baseline, s, nBytes, color, alpha, clip);
if (cut) {
int pre = textWidth16(f, s, nBytes);
drawGlyphs(buf, f, x0 + (pre + 8) / 16, baseline, ell, 3, color, alpha, clip);
}
}
// Baseline of a text line of height lineH, centered like CSS line-height
static int baselineFor(const VFont& f, int top, int lineH) {
return top + (lineH - (f.ascent + f.descent)) / 2 + f.ascent;
}
// =====================================================================
// Bluetooth polling
// =====================================================================
static void pollBle() {
if (pollBusy || inPairingUi) return; // never re-enter ArduinoBLE from its own callbacks
pollBusy = true;
BLE.poll();
pollBusy = false;
}
// =====================================================================
// Display: partial updates
// =====================================================================
static void flushRect(Rect r) {
r = rectClip(r);
if (rectEmpty(r)) return;
int w = r.x1 - r.x0, h = r.y1 - r.y0;
tft->startWrite();
tft->writeAddrWindow(r.x0, r.y0, w, h);
for (int j = 0; j < h; j++) {
bus->writePixels(&fb[(r.y0 + j) * SCR_W + r.x0], w);
if ((j & 15) == 15) { tft->endWrite(); pollBle(); tft->startWrite(); }
}
tft->endWrite();
}
static void copyBg(Rect r) {
r = rectClip(r);
if (rectEmpty(r)) return;
int w = r.x1 - r.x0;
for (int y = r.y0; y < r.y1; y++) memcpy(&fb[y * SCR_W + r.x0], &bgL[y * SCR_W + r.x0], w * 2);
}
// =====================================================================
// Static layer
// =====================================================================
static inline void px8(int x, int y, float r, float g, float b, float a) {
if (a <= 0.0f) return;
if (a > 1.0f) a = 1.0f;
uint8_t* p = &bg8[(y * SCR_W + x) * 3];
p[0] = (uint8_t)(p[0] + (r - p[0]) * a + 0.5f);
p[1] = (uint8_t)(p[1] + (g - p[1]) * a + 0.5f);
p[2] = (uint8_t)(p[2] + (b - p[2]) * a + 0.5f);
}
static void buildBackground() {
const Rect full = {0, 0, SCR_W, SCR_H};
// 1) Gradients and shadows at 8 bits per channel, to avoid banding
for (int y = 0; y < SCR_H; y++) {
float dark = 0.0f;
if (y >= 192) dark = 0.35f * (y - 192) / 128.0f;
if ((y & 7) == 0) pollBle();
for (int x = 0; x < SCR_W; x++) {
float dx = x + 0.5f - DISC_CX, dy = y + 0.5f - DISC_CY;
float d = sqrtf(dx * dx + dy * dy);
float a = d < 170.0f ? 0.07f * (1.0f - d / 170.0f) : 0.0f;
float r = bgR + (255 - bgR) * a, g = bgG + (255 - bgG) * a, b = bgB + (255 - bgB) * a;
uint8_t* p = &bg8[(y * SCR_W + x) * 3];
p[0] = (uint8_t)(r * (1 - dark) + 0.5f);
p[1] = (uint8_t)(g * (1 - dark) + 0.5f);
p[2] = (uint8_t)(b * (1 - dark) + 0.5f);
}
}
// Cover shadow: 10 px down, 24 px blur, 45% black
for (int y = 0; y < 240; y++) {
if ((y & 7) == 0) pollBle();
for (int x = 0; x < 230; x++) {
float d = sdRoundBox(x + 0.5f, y + 0.5f, COVER_X + 78.0f, COVER_Y + 78.0f + 10.0f, 78.0f, 78.0f, 10.0f);
px8(x, y, 0, 0, 0, 0.45f * shadowA(d, 12.0f));
}
}
// Record shadow: offset (-4, 16) px, 28 px blur, 55% black
for (int y = 0; y < SCR_H; y++) {
if ((y & 7) == 0) pollBle();
for (int x = 170; x < SCR_W; x++) {
float dx = x + 0.5f - 336.0f, dy = y + 0.5f - 136.0f;
float d = sqrtf(dx * dx + dy * dy) - DISC_R;
px8(x, y, 0, 0, 0, 0.55f * shadowA(d, 14.0f));
}
}
// Record: grooves, rim, label, spindle hole and a fixed sheen
for (int y = (int)(DISC_CY - DISC_R - 2); y < (int)(DISC_CY + DISC_R + 2); y++) {
if ((y & 7) == 0) pollBle();
for (int x = (int)(DISC_CX - DISC_R - 2); x < (int)(DISC_CX + DISC_R + 2); x++) {
if (x < 0 || x >= SCR_W || y < 0 || y >= SCR_H) continue;
float dx = x + 0.5f - DISC_CX, dy = y + 0.5f - DISC_CY;
float d = sqrtf(dx * dx + dy * dy);
float cOuter = cov(d - DISC_R);
if (cOuter <= 0) continue;
float v = (fmodf(d, 3.0f) < 1.0f) ? 0x12 : 0x1c; // grooves
float e = 1.0f - cov((DISC_R - 2.0f) - d); // 2 px rim
v = v + (0x0a - v) * e;
float l = cov(d - LABEL_R); // label
v = v + (0x4a - v) * l;
float h = cov(d - HOLE_R); // spindle hole
v = v + (0x0a - v) * h;
float ang = atan2f(dx, -dy) * 57.2957795f; // fixed conic sheen
if (ang < 0) ang += 360.0f;
float t = fmodf(ang - 20.0f + 360.0f, 360.0f);
float sh = 0;
if (t < 30) sh = 0.10f * t / 30.0f;
else if (t < 60) sh = 0.10f * (60 - t) / 30.0f;
else if (t >= 180 && t < 210) sh = 0.07f * (t - 180) / 30.0f;
else if (t >= 210 && t < 240) sh = 0.07f * (240 - t) / 30.0f;
v = v + (255 - v) * sh;
px8(x, y, v, v, v, cOuter);
}
}
// Button shadow: 6 px down, 14 px blur, 35% black
for (int y = 230; y < SCR_H; y++)
for (int x = 0; x < 110; x++) {
float dx = x + 0.5f - BTN_CX, dy = y + 0.5f - (BTN_CY + 6.0f);
float d = sqrtf(dx * dx + dy * dy) - BTN_R;
px8(x, y, 0, 0, 0, 0.35f * shadowA(d, 7.0f));
}
// 2) Convert to RGB565 with ordered dithering, so gradients don't band
static const uint8_t bayer[4][4] = {{0, 8, 2, 10}, {12, 4, 14, 6}, {3, 11, 1, 9}, {15, 7, 13, 5}};
for (int y = 0; y < SCR_H; y++)
for (int x = 0; x < SCR_W; x++) {
const uint8_t* p = &bg8[(y * SCR_W + x) * 3];
int bv = bayer[y & 3][x & 3];
int r = (p[0] + (bv >> 1)) >> 3; if (r > 31) r = 31;
int g = (p[1] + (bv >> 2)) >> 2; if (g > 63) g = 63;
int b = (p[2] + (bv >> 1)) >> 3; if (b > 31) b = 31;
bgL[y * SCR_W + x] = (r << 11) | (g << 5) | b;
}
// 3) Album art with rounded corners
uint16_t ph = rgb565(58, 58, 60);
for (int j = 0; j < COVER_S; j++) {
for (int i = 0; i < COVER_S; i++) {
int x = COVER_X + i, y = COVER_Y + j;
float d = sdRoundBox(x + 0.5f, y + 0.5f, COVER_X + 78.0f, COVER_Y + 78.0f, 78.0f, 78.0f, 10.0f);
float c = cov(d);
if (c <= 0) continue;
uint16_t src = coverValid ? coverBuf[j * COVER_S + i] : ph;
bgL[y * SCR_W + x] = blend565(bgL[y * SCR_W + x], src, c);
}
}
// 4) Title and artist
drawText(bgL, F_TITLE, TITLE_X, baselineFor(F_TITLE, TITLE_TOP, 22), uiTitle, 0xFFFF, 1.0f, TEXT_MAXW, 0, full);
drawText(bgL, F_BODY, TITLE_X, baselineFor(F_BODY, TITLE_TOP + 22 + 4, 18), uiArtist, 0xFFFF, 0.75f, TEXT_MAXW, 0, full);
}
// =====================================================================
// Animated elements
// =====================================================================
static Rect labelRect() {
Rect r = { (int16_t)(DISC_CX - LABEL_R - 2), (int16_t)(DISC_CY - LABEL_R - 2),
(int16_t)(DISC_CX + LABEL_R + 2), (int16_t)(DISC_CY + LABEL_R + 2) };
return r;
}
// Grey line across the label, split around the hole, so the spin is visible
static void drawLabelLine(Rect clip) {
Rect area = rectIntersect(clip, labelRect());
if (rectEmpty(area)) return;
float th = discDeg * 0.0174532925f;
float cs = cosf(th), sn = sinf(th);
const uint16_t lineC = rgb565(0x8c, 0x8c, 0x8c);
for (int y = area.y0; y < area.y1; y++) {
for (int x = area.x0; x < area.x1; x++) {
float dx = x + 0.5f - DISC_CX, dy = y + 0.5f - DISC_CY;
float lx = dx * cs + dy * sn;
float ly = -dx * sn + dy * cs;
float dA = sdRoundBox(lx, ly, -18.0f, 0.0f, 11.0f, 1.0f, 1.0f);
float dB = sdRoundBox(lx, ly, 18.0f, 0.0f, 11.0f, 1.0f, 1.0f);
float c = cov(dA < dB ? dA : dB);
if (c > 0) fb[y * SCR_W + x] = blend565(fb[y * SCR_W + x], lineC, c);
}
}
}
static Rect armRectFor(float deg) {
float th = deg * 0.0174532925f, cs = cosf(th), sn = sinf(th);
const float lx[4] = {-24, 28, 28, -24}, ly[4] = {-26, -26, 190, 190};
float minx = 1e9, miny = 1e9, maxx = -1e9, maxy = -1e9;
for (int i = 0; i < 4; i++) {
float x = ARM_PX + lx[i] * cs - ly[i] * sn;
float y = ARM_PY + lx[i] * sn + ly[i] * cs;
if (x < minx) minx = x; if (x > maxx) maxx = x;
if (y < miny) miny = y; if (y > maxy) maxy = y;
}
Rect r = { (int16_t)floorf(minx), (int16_t)floorf(miny), (int16_t)(ceilf(maxx) + 1), (int16_t)(ceilf(maxy) + 1) };
return rectClip(r);
}
// Tonearm: rod, headshell and pivot base (ring and dot), with shadows
static void drawArm(Rect clip) {
Rect area = rectIntersect(clip, armRectFor(armDeg));
if (rectEmpty(area)) return;
float th = armDeg * 0.0174532925f, cs = cosf(th), sn = sinf(th);
const uint16_t cRod = rgb565(0xc9, 0xc9, 0xc9), cHead = rgb565(0xe6, 0xe6, 0xe6);
const uint16_t cBase = rgb565(0x2a, 0x2a, 0x2a), cRing = rgb565(0xbd, 0xbd, 0xbd);
for (int y = area.y0; y < area.y1; y++) {
for (int x = area.x0; x < area.x1; x++) {
float dx = x + 0.5f - ARM_PX, dy = y + 0.5f - ARM_PY;
float lx = dx * cs + dy * sn;
float ly = -dx * sn + dy * cs;
float dRod = sdRoundBox(lx, ly, 0.0f, 78.0f, 3.0f, 78.0f, 3.0f);
float dHead = sdRoundBox(lx, ly, 0.0f, 164.0f, 8.0f, 14.0f, 3.0f);
float dBase = sqrtf(lx * lx + ly * ly) - 20.0f;
if (dRod > 12 && dHead > 12 && dBase > 16) continue;
uint16_t* p = &fb[y * SCR_W + x];
uint16_t c = *p;
// shadows
float sRod = 0.45f * shadowA(sdRoundBox(lx - 2, ly - 4, 0.0f, 78.0f, 3.0f, 78.0f, 3.0f), 3.0f);
float sHead = 0.45f * shadowA(sdRoundBox(lx - 2, ly - 4, 0.0f, 164.0f, 8.0f, 14.0f, 3.0f), 3.0f);
float sBase = 0.50f * shadowA(sqrtf(lx * lx + (ly - 4) * (ly - 4)) - 20.0f, 4.0f);
float s = sRod > sHead ? sRod : sHead;
if (sBase > s) s = sBase;
c = blend565(c, 0x0000, s);
c = blend565(c, cRod, cov(dRod));
c = blend565(c, cHead, cov(dHead));
c = blend565(c, cBase, cov(dBase));
float ring = (dBase > -3.0f) ? cov(dBase) * clampf(dBase + 3.5f, 0, 1) : 0.0f;
c = blend565(c, cRing, ring);
c = blend565(c, cRing, cov(sqrtf(lx * lx + ly * ly) - 6.0f));
*p = c;
}
}
}
static Rect buttonRect() {
Rect r = { (int16_t)(BTN_CX - BTN_R - 1), (int16_t)(BTN_CY - BTN_R - 1),
(int16_t)(BTN_CX + BTN_R + 2), (int16_t)(BTN_CY + BTN_R + 2) };
return r;
}
static bool uiPlaying() { return amsReady && amsState == 1; }
static void drawButton(Rect clip) {
Rect area = rectIntersect(clip, buttonRect());
if (rectEmpty(area)) return;
bool playing = uiPlaying();
uint16_t iconC = rgb565(bgR, bgG, bgB);
for (int y = area.y0; y < area.y1; y++) {
for (int x = area.x0; x < area.x1; x++) {
float px = x + 0.5f, py = y + 0.5f;
float dx = px - BTN_CX, dy = py - BTN_CY;
float c = cov(sqrtf(dx * dx + dy * dy) - BTN_R);
if (c <= 0) continue;
uint16_t col = 0xFFFF;
float ic;
if (playing) {
float a = sdRoundBox(px, py, 46.0f, 276.0f, 2.0f, 7.0f, 1.0f);
float b = sdRoundBox(px, py, 54.0f, 276.0f, 2.0f, 7.0f, 1.0f);
ic = cov(a < b ? a : b);
} else {
ic = cov(sdTriangle(px, py, 46.0f, 269.5f, 56.5f, 276.0f, 46.0f, 282.5f) - 0.75f);
}
col = blend565(col, iconC, ic);
fb[y * SCR_W + x] = blend565(fb[y * SCR_W + x], col, c);
}
}
}
static Rect barRect() {
Rect r = { (int16_t)BAR_X, (int16_t)BAR_Y, (int16_t)(BAR_X + BAR_W), (int16_t)(BAR_Y + BAR_H) };
return r;
}
static float currentProgress() {
if (!amsReady || amsDuration <= 0.5f) return 0.0f;
float e = amsElapsed;
if (amsState == 1) e += amsRate * (millis() - amsElapsedAt) / 1000.0f;
return clampf(e / amsDuration, 0.0f, 1.0f);
}
static void drawBar(Rect clip, float fillW) {
Rect area = rectIntersect(clip, barRect());
if (rectEmpty(area)) return;
for (int y = area.y0; y < area.y1; y++) {
for (int x = area.x0; x < area.x1; x++) {
float px = x + 0.5f, py = y + 0.5f;
float t = cov(sdRoundBox(px, py, BAR_X + BAR_W / 2.0f, BAR_Y + 3.0f, BAR_W / 2.0f, 3.0f, 3.0f));
if (t <= 0) continue;
uint16_t* p = &fb[y * SCR_W + x];
uint16_t c = blend565(*p, 0xFFFF, 0.22f * t);
if (fillW > 0.1f) {
float hw = fillW / 2.0f;
float f = cov(sdRoundBox(px, py, BAR_X + hw, BAR_Y + 3.0f, hw, 3.0f, hw < 3.0f ? hw : 3.0f));
c = blend565(c, 0xFFFF, f * t);
}
*p = c;
}
}
}
static void composeRect(Rect r, float fillW) {
r = rectClip(r);
if (rectEmpty(r)) return;
pollBle();
copyBg(r);
drawLabelLine(r);
drawArm(r);
drawButton(r);
drawBar(r, fillW);
flushRect(r);
pollBle();
}
// =====================================================================
// Pairing screen
// =====================================================================
const Rect BTN_CANCEL = {100, 230, 230, 282};
const Rect BTN_CONFIRM = {250, 230, 380, 282};
static void drawPairingScreen(uint32_t code) {
const Rect full = {0, 0, SCR_W, SCR_H};
uint16_t bgc = rgb565(0x11, 0x11, 0x11);
for (int i = 0; i < SCR_W * SCR_H; i++) fb[i] = bgc;
drawText(fb, F_TITLE, SCR_W / 2, 70, "Pair with iPhone", 0xFFFF, 1.0f, 0, 1, full);
char c[8];
snprintf(c, sizeof(c), "%03lu %03lu", (unsigned long)(code / 1000) % 1000, (unsigned long)(code % 1000));
drawText(fb, F_CODE, SCR_W / 2, 150, c, 0xFFFF, 1.0f, 0, 1, full);
drawText(fb, F_BODY, SCR_W / 2, 190, "Check that your iPhone shows the same code", 0xFFFF, 0.75f, 0, 1, full);
// Buttons
for (int y = 228; y < 285; y++) {
for (int x = 95; x < 385; x++) {
float px = x + 0.5f, py = y + 0.5f;
float dC = sdRoundBox(px, py, 165.0f, 256.0f, 65.0f, 26.0f, 26.0f);
float ring = cov(dC) * clampf(dC + 2.5f, 0, 1);
fb[y * SCR_W + x] = blend565(fb[y * SCR_W + x], 0xFFFF, 0.5f * ring);
float dK = sdRoundBox(px, py, 315.0f, 256.0f, 65.0f, 26.0f, 26.0f);
fb[y * SCR_W + x] = blend565(fb[y * SCR_W + x], 0xFFFF, cov(dK));
}
}
drawText(fb, F_BTN, 165, baselineFor(F_BTN, 230, 52), "Cancel", 0xFFFF, 1.0f, 0, 1, full);
drawText(fb, F_BTN, 315, baselineFor(F_BTN, 230, 52), "Confirm", bgc, 1.0f, 0, 1, full);
flushRect(full);
}
// =====================================================================
// FT6336U touch panel
// =====================================================================
static bool readTouch(int& x, int& y) {
Wire.beginTransmission(CTP_ADDR);
Wire.write(0x02);
if (Wire.endTransmission(false) != 0) return false;
if (Wire.requestFrom(CTP_ADDR, 5) != 5) return false;
uint8_t n = Wire.read() & 0x0F;
uint8_t xh = Wire.read(), xl = Wire.read(), yh = Wire.read(), yl = Wire.read();
if (n == 0 || n > 2) return false;
int rx = ((xh & 0x0F) << 8) | xl;
int ry = ((yh & 0x0F) << 8) | yl;
if (rx > 319 || ry > 479) return false;
#if TFT_ROTATION == 1
x = ry; y = 319 - rx;
#elif TFT_ROTATION == 3
x = 479 - ry; y = rx;
#else
#error "TFT_ROTATION must be 1 or 3 (landscape)"
#endif
return true;
}
static bool inRect(int x, int y, Rect r) { return x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1; }
// =====================================================================
// Encrypted bond storage (internal flash)
// =====================================================================
static const uint8_t BOND_MAGIC[4] = {'V', 'N', 'B', '1'};
#define BOND_REC_SIZE 96 // 4 magic + 12 IV + 38 data + 16 tag, padded to 96
extern uint32_t __etext;
extern uint32_t __data_start__;
extern uint32_t __data_end__;
static void deriveKey(uint8_t key[32]) {
uint8_t uid[12];
uint32_t w0 = HAL_GetUIDw0(), w1 = HAL_GetUIDw1(), w2 = HAL_GetUIDw2();
memcpy(uid, &w0, 4); memcpy(uid + 4, &w1, 4); memcpy(uid + 8, &w2, 4);
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts_ret(&ctx, 0);
const char* label = "VinylPlayer bond key v1";
mbedtls_sha256_update_ret(&ctx, (const uint8_t*)label, strlen(label));
mbedtls_sha256_update_ret(&ctx, uid, sizeof(uid));
mbedtls_sha256_update_ret(&ctx, (const uint8_t*)SECRET_STORAGE_KEY, strlen(SECRET_STORAGE_KEY));
mbedtls_sha256_finish_ret(&ctx, key);
mbedtls_sha256_free(&ctx);
mbedtls_platform_zeroize(uid, sizeof(uid));
}
static bool randomBytes(uint8_t* out, size_t len) {
trng_t t;
trng_init(&t);
size_t got = 0;
bool ok = true;
while (got < len && ok) {
size_t n = 0;
if (trng_get_bytes(&t, out + got, len - got, &n) != 0) ok = false;
got += n;
}
trng_free(&t);
return ok && got == len;
}
static void storageInit() {
storageUsable = false;
if (strlen(SECRET_STORAGE_KEY) < 32 || strstr(SECRET_STORAGE_KEY, "CHANGE_ME")) {
Serial.println("[security] Set your own SECRET_STORAGE_KEY (32+ characters) in arduino_secrets.h. Pairing will not be saved.");
return;
}
if (flash.init() != 0) return;
uint32_t start = flash.get_flash_start(), size = flash.get_flash_size();
uint32_t last = start + size - 1;
storageSize = flash.get_sector_size(last);
storageAddr = start + size - storageSize;
uint32_t imageEnd = (uint32_t)&__etext + ((uint32_t)&__data_end__ - (uint32_t)&__data_start__);
if (imageEnd >= storageAddr) { Serial.println("[flash] The sketch reaches the last flash sector. Pairing will not be saved."); return; }
// Use the sector only if it is blank or already ours; never overwrite foreign data.
uint8_t head[BOND_REC_SIZE];
flash.read(head, storageAddr, sizeof(head));
bool ours = memcmp(head, BOND_MAGIC, 4) == 0;
bool empty = true;
for (uint32_t off = 0; off < storageSize && empty; off += sizeof(head)) {
uint8_t chunk[BOND_REC_SIZE];
flash.read(chunk, storageAddr + off, sizeof(chunk));
for (size_t i = 0; i < sizeof(chunk); i++) if (chunk[i] != 0xFF) { empty = false; break; }
if (ours) break;
}
if (!ours && !empty) { Serial.println("[flash] The last flash sector holds other data and is left alone. Pairing will not be saved."); return; }
storageUsable = true;
if (!ours) return;
uint8_t key[32];
deriveKey(key);
mbedtls_gcm_context g;
mbedtls_gcm_init(&g);
uint8_t plain[sizeof(BondRecord)];
bool ok = mbedtls_gcm_setkey(&g, MBEDTLS_CIPHER_ID_AES, key, 256) == 0 &&
mbedtls_gcm_auth_decrypt(&g, sizeof(BondRecord), head + 4, 12, BOND_MAGIC, 4,
head + 4 + 12 + sizeof(BondRecord), 16,
head + 4 + 12, plain) == 0;
mbedtls_gcm_free(&g);
mbedtls_platform_zeroize(key, sizeof(key));
if (ok) { memcpy(&bond, plain, sizeof(bond)); hasBond = true; }
mbedtls_platform_zeroize(plain, sizeof(plain));
}
static bool storageWrite(const BondRecord* rec) {
if (!storageUsable) return false;
if (flash.erase(storageAddr, storageSize) != 0) return false;
if (!rec) return true;
alignas(4) uint8_t out[BOND_REC_SIZE]; // word-aligned source for flash programming
memset(out, 0xFF, sizeof(out));
memcpy(out, BOND_MAGIC, 4);
if (!randomBytes(out + 4, 12)) return false;
uint8_t key[32];
deriveKey(key);
mbedtls_gcm_context g;
mbedtls_gcm_init(&g);
bool ok = mbedtls_gcm_setkey(&g, MBEDTLS_CIPHER_ID_AES, key, 256) == 0 &&
mbedtls_gcm_crypt_and_tag(&g, MBEDTLS_GCM_ENCRYPT, sizeof(BondRecord), out + 4, 12, BOND_MAGIC, 4,
(const uint8_t*)rec, out + 4 + 12, 16, out + 4 + 12 + sizeof(BondRecord)) == 0;
mbedtls_gcm_free(&g);
mbedtls_platform_zeroize(key, sizeof(key));
if (!ok) return false;
uint32_t page = flash.get_page_size();
uint32_t len = ((sizeof(out) + page - 1) / page) * page;
if (len > sizeof(out)) return false;
return flash.program(out, storageAddr, len) == 0;
}
// =====================================================================
// ArduinoBLE pairing callbacks
// =====================================================================
static int cbGetIRKs(uint8_t* nIRKs, uint8_t** BDAddrType, uint8_t*** BDAddrs, uint8_t*** IRKs) {
// The library always frees these arrays, so they must be allocated even when empty.
*nIRKs = hasBond ? 1 : 0;
*BDAddrType = new uint8_t[1];
*BDAddrs = new uint8_t*[1];
*IRKs = new uint8_t*[1];
if (hasBond) {
(*BDAddrType)[0] = 0;
(*BDAddrs)[0] = new uint8_t[6];
(*IRKs)[0] = new uint8_t[16];
memcpy((*BDAddrs)[0], bond.addr, 6);
memcpy((*IRKs)[0], bond.irk, 16);
}
return 1;
}
static int cbGetLTK(uint8_t* address, uint8_t* LTK) {
if (hasBond && memcmp(address, bond.addr, 6) == 0) {
memcpy(LTK, bond.ltk, 16);
return 1;
}
return 0;
}
static int cbStoreIRK(uint8_t* address, uint8_t* IRK) {
memcpy(pendingBond.addr, address, 6);
memcpy(pendingBond.irk, IRK, 16);
return 1;
}
static int cbStoreLTK(uint8_t* address, uint8_t* LTK) {
if (hasBond) return 0; // already paired: refuse a second iPhone
memcpy(pendingBond.addr, address, 6);
memcpy(pendingBond.ltk, LTK, 16);
bond = pendingBond;
mbedtls_platform_zeroize(&pendingBond, sizeof(pendingBond));
hasBond = true;
bondDirty = true; // saved to flash by the network thread
BLE.setPairable(Pairable::NO);
Serial.println("[BLE] iPhone paired");
return 1;
}
static void cbDisplayCode(uint32_t code) {
inPairingUi = true;
Serial.println("[BLE] Pairing code shown on screen");
pairCode = code % 1000000;
drawPairingScreen(pairCode);
pairingShown = true;
inPairingUi = false;
}
static bool cbConfirmPairing() {
inPairingUi = true;
if (!pairingShown) drawPairingScreen(pairCode);
uint32_t t0 = millis();
bool result = false;
bool decided = false;
bool wasDown = true; // wait for the finger to lift first
while (!decided && millis() - t0 < 30000) {
int x, y;
bool down = readTouch(x, y);
if (down && !wasDown) {
if (inRect(x, y, BTN_CONFIRM)) { result = true; decided = true; }
else if (inRect(x, y, BTN_CANCEL)) { result = false; decided = true; }
}
wasDown = down;
delay(20);
}
pairingShown = false;
needFull = true;
inPairingUi = false;
Serial.println(result ? "[BLE] Pairing confirmed on the board" : "[BLE] Pairing cancelled on the board");
if (result) setStatus("Pairing\xE2\x80\xA6", "Confirm on your iPhone too");
return result;
}
// =====================================================================
// Network: HTTPS and album art (network thread)
// =====================================================================
// Root CAs for itunes.apple.com and the artwork CDN (*.mzstatic.com)
static const char APPLE_ROOT_CAS[] =
// DigiCert Global Root G2
"-----BEGIN CERTIFICATE-----\n"
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
"MrY=\n"
"-----END CERTIFICATE-----\n"
// DigiCert Global Root G3
"-----BEGIN CERTIFICATE-----\n"
"MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw\n"
"CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\n"
"ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe\n"
"Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw\n"
"EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x\n"
"IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF\n"
"K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG\n"
"fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO\n"
"Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd\n"
"BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx\n"
"AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/\n"
"oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8\n"
"sycX\n"
"-----END CERTIFICATE-----\n"
;
static bool containsNoCase(const char* h, const char* n) {
size_t ln = strlen(n);
for (; *h; h++) if (strncasecmp(h, n, ln) == 0) return true;
return false;
}
static int readByteT(WiFiSSLClient& c, uint32_t deadline) {
while (millis() < deadline) {
if (c.available()) return c.read();
if (!c.connected()) return -1;
rtos::ThisThread::sleep_for(2ms);
}
return -1;
}
static int readLineT(WiFiSSLClient& c, char* out, int max, uint32_t deadline) {
int n = 0;
while (true) {
int b = readByteT(c, deadline);
if (b < 0) return n > 0 ? n : -1;
if (b == '\n') break;
if (b == '\r') continue;
if (n < max - 1) out[n++] = (char)b;
}
out[n] = 0;
return n;
}
// HTTPS GET with certificate verification. Returns the HTTP status, or < 0 on error.
static int httpsGetOnce(const char* host, const char* path, uint8_t* out, size_t maxLen, size_t& outLen,
char* location, size_t locLen, bool ownRoots) {
outLen = 0;
if (location && locLen) location[0] = 0;
WiFiSSLClient cli;
if (ownRoots) cli.setCACert(APPLE_ROOT_CAS); // trust only the roots above
if (!cli.connect(host, 443)) return -1;
cli.print("GET "); cli.print(path); cli.print(" HTTP/1.1\r\n");
cli.print("Host: "); cli.print(host); cli.print("\r\n");
cli.print("User-Agent: VinylPlayer/1.0\r\nAccept: */*\r\nAccept-Encoding: identity\r\nConnection: close\r\n\r\n");
uint32_t deadline = millis() + 12000;
char line[512];
if (readLineT(cli, line, sizeof(line), deadline) < 0) { cli.stop(); return -2; }
int status = 0;
if (strncmp(line, "HTTP/1.", 7) != 0 || strlen(line) < 12) { cli.stop(); return -3; }
status = atoi(line + 9);
bool chunked = false;
long contentLen = -1;
for (int h = 0; h < 80; h++) {
int n = readLineT(cli, line, sizeof(line), deadline);
if (n < 0) { cli.stop(); return -4; }
if (n == 0) break;
if (!strncasecmp(line, "Transfer-Encoding:", 18) && containsNoCase(line, "chunked")) chunked = true;
else if (!strncasecmp(line, "Content-Length:", 15)) contentLen = atol(line + 15);
else if (!strncasecmp(line, "Location:", 9) && location) {
const char* v = line + 9;
while (*v == ' ') v++;
strncpy(location, v, locLen - 1);
location[locLen - 1] = 0;
}
}
if (status != 200) { cli.stop(); return status; }
if (contentLen > (long)maxLen) { cli.stop(); return -5; }
if (chunked) {
while (true) {
if (readLineT(cli, line, sizeof(line), deadline) < 0) { cli.stop(); return -6; }
long sz = strtol(line, nullptr, 16);
if (sz < 0) { cli.stop(); return -6; }
if (sz == 0) break;
if (outLen + sz > maxLen) { cli.stop(); return -5; }
for (long i = 0; i < sz; i++) {
int b = readByteT(cli, deadline);
if (b < 0) { cli.stop(); return -7; }
out[outLen++] = (uint8_t)b;
}
readLineT(cli, line, sizeof(line), deadline);
}
} else {
while (contentLen < 0 || (long)outLen < contentLen) {
int b = readByteT(cli, deadline);
if (b < 0) break;
if (outLen >= maxLen) { cli.stop(); return -5; }
out[outLen++] = (uint8_t)b;
}
if (contentLen >= 0 && (long)outLen != contentLen) { cli.stop(); return -8; }
}
cli.stop();
return 200;
}
// Tries the bundled roots first, then the board's CA store
static int httpsGet(const char* host, const char* path, uint8_t* out, size_t maxLen, size_t& outLen,
char* location, size_t locLen) {
int st = httpsGetOnce(host, path, out, maxLen, outLen, location, locLen, true);
if (st == -1) {
Serial.print("[NET] HTTPS with the bundled roots failed for "); Serial.print(host); Serial.println(", trying the board's CA store");
st = httpsGetOnce(host, path, out, maxLen, outLen, location, locLen, false);
if (st == -1) { Serial.print("[NET] Could not open HTTPS to "); Serial.println(host); }
}
return st;
}
static void urlEncodeAppend(char* dst, size_t cap, const char* src) {
size_t n = strlen(dst);
for (const uint8_t* p = (const uint8_t*)src; *p && n + 4 < cap; p++) {
uint8_t c = *p;
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') dst[n++] = c;
else if (c == ' ') dst[n++] = '+';
else { snprintf(dst + n, 4, "%%%02X", c); n += 3; }
}
dst[n] = 0;
}
// Splits https://host/path and checks that the host is Apple's artwork CDN
static bool splitArtUrl(const char* url, char* host, size_t hostCap, char* path, size_t pathCap) {
if (strncmp(url, "https://", 8) != 0) return false;
const char* h = url + 8;
const char* slash = strchr(h, '/');
if (!slash) return false;
size_t hl = slash - h;
if (hl == 0 || hl >= hostCap) return false;
memcpy(host, h, hl);
host[hl] = 0;
for (size_t i = 0; i < hl; i++) {
char c = host[i];
if (!(isalnum((uint8_t)c) || c == '-' || c == '.')) return false;
}
const char* suffix = ".mzstatic.com";
if (hl <= strlen(suffix) || strcmp(host + hl - strlen(suffix), suffix) != 0) return false;
if (strlen(slash) >= pathCap) return false;
strcpy(path, slash);
return true;
}
static int jpegDraw(JPEGDRAW* d) {
for (int j = 0; j < d->iHeight; j++) {
int y = d->y + j;
if (y < 0 || y >= decH) continue;
for (int i = 0; i < d->iWidth; i++) {
int x = d->x + i;
if (x < 0 || x >= decW) continue;
decBuf[y * DEC_MAX + x] = d->pPixels[j * d->iWidth + i];
}
}
return 1;
}
static bool decodeCover(size_t len) {
if (!jpeg.openRAM(jpgBuf, (int)len, jpegDraw)) return false;
int w = jpeg.getWidth(), h = jpeg.getHeight();
if (w <= 0 || h <= 0 || w > 4096 || h > 4096) { jpeg.close(); return false; }
int opt = 0, sc = 1;
if (jpeg.getJPEGType() == JPEG_MODE_PROGRESSIVE) sc = 8; // JPEGDEC decodes these at 1/8 size only
while ((w / sc > DEC_MAX || h / sc > DEC_MAX) && sc < 8) sc *= 2;
if (w / sc > DEC_MAX || h / sc > DEC_MAX) { jpeg.close(); return false; }
if (sc == 2) opt = JPEG_SCALE_HALF;
else if (sc == 4) opt = JPEG_SCALE_QUARTER;
else if (sc == 8) opt = JPEG_SCALE_EIGHTH;
decW = w / sc;
decH = h / sc;
jpeg.setPixelType(RGB565_LITTLE_ENDIAN);
int ok = jpeg.decode(0, 0, opt);
jpeg.close();
if (!ok || decW < 8 || decH < 8) return false;
// Center-crop to a square and box-filter it down to 156x156
int side = decW < decH ? decW : decH;
int ox = (decW - side) / 2, oy = (decH - side) / 2;
uint32_t sr = 0, sg = 0, sb = 0;
for (int j = 0; j < COVER_S; j++) {
int y0 = oy + j * side / COVER_S, y1 = oy + (j + 1) * side / COVER_S;
if (y1 <= y0) y1 = y0 + 1;
for (int i = 0; i < COVER_S; i++) {
int x0 = ox + i * side / COVER_S, x1 = ox + (i + 1) * side / COVER_S;
if (x1 <= x0) x1 = x0 + 1;
uint32_t r = 0, g = 0, b = 0, n = 0;
for (int y = y0; y < y1; y++) for (int x = x0; x < x1; x++) {
uint16_t p = decBuf[y * DEC_MAX + x];
r += (p >> 11) << 3; g += ((p >> 5) & 63) << 2; b += (p & 31) << 3; n++;
}
r /= n; g /= n; b /= n;
netCover[j * COVER_S + i] = rgb565(r, g, b);
sr += r; sg += g; sb += b;
}
}
const uint32_t total = COVER_S * COVER_S;
netAvgR = sr / total; netAvgG = sg / total; netAvgB = sb / total;
return true;
}
static bool fetchArt(const ArtRequest& rq) {
static char path[700];
char host[64], apath[400], url[400], loc[400];
size_t got = 0;
const char* q2 = rq.album[0] ? rq.album : rq.title;
bool songSearch = rq.album[0] == 0;
strcpy(path, "/search?term=");
urlEncodeAppend(path, 300, rq.artist);
urlEncodeAppend(path, 300, " ");
urlEncodeAppend(path, 300, q2);
strcat(path, songSearch ? "&media=music&entity=song&limit=1" : "&media=music&entity=album&limit=1");
Serial.println("[NET] Looking up the cover on iTunes");
int st = httpsGet("itunes.apple.com", path, (uint8_t*)jsonBuf, sizeof(jsonBuf) - 1, got, nullptr, 0);
if (st != 200) { Serial.print("[NET] iTunes returned status "); Serial.println(st); return false; }
jsonBuf[got] = 0;
const char* key = "\"artworkUrl100\":\"";
char* p = strstr(jsonBuf, key);
if (!p) { Serial.print("[NET] No cover found on iTunes ("); Serial.print((int)got); Serial.println(" bytes)"); return false; }
p += strlen(key);
size_t n = 0;
while (*p && *p != '"' && n < sizeof(url) - 1) {
if (p[0] == '\\' && p[1] == '/') { url[n++] = '/'; p += 2; continue; }
if (*p == '\\') return false;
url[n++] = *p++;
}
url[n] = 0;
char* sz = strstr(url, "100x100");
if (sz) memcpy(sz, "156x156", 7);
if (!splitArtUrl(url, host, sizeof(host), apath, sizeof(apath))) { Serial.println("[NET] Invalid cover URL"); return false; }
Serial.println("[NET] Downloading the cover");
for (int hop = 0; hop < 2; hop++) {
st = httpsGet(host, apath, jpgBuf, JPG_MAX, got, loc, sizeof(loc));
if (st == 200) break;
if ((st == 301 || st == 302 || st == 307 || st == 308) && loc[0]) {
if (!splitArtUrl(loc, host, sizeof(host), apath, sizeof(apath))) return false;
continue;
}
return false;
}
if (st != 200 || got < 4) { Serial.print("[NET] Cover download failed, status "); Serial.println(st); return false; }
if (!decodeCover(got)) { Serial.print("[NET] Could not decode the cover ("); Serial.print((int)got); Serial.println(" bytes)"); return false; }
Serial.println("[NET] Cover ready");
return true;
}
static void netTask() {
uint32_t lastWifiTry = 0;
bool wifiTried = false;
while (true) {
if (storageOp) {
netMtx.lock();
int op = storageOp;
BondRecord rec = storageRec;
mbedtls_platform_zeroize(&storageRec, sizeof(storageRec));
netMtx.unlock();
bool ok = storageWrite(op == 1 ? &rec : nullptr);
mbedtls_platform_zeroize(&rec, sizeof(rec));
netMtx.lock();
if (storageOp == op) storageOp = 0; // a newer request (e.g. forget) stays queued
netMtx.unlock();
if (op == 1) Serial.println(ok ? "[flash] Pairing saved (encrypted)" : "[flash] Could not save the pairing; it lasts until reboot");
}
if (WiFi.status() != WL_CONNECTED) {
if (!wifiTried || millis() - lastWifiTry > 15000) {
wifiTried = true;
lastWifiTry = millis();
Serial.println("[NET] Connecting to Wi-Fi...");
int ws = WiFi.begin(SECRET_SSID, SECRET_PASS);
if (ws == WL_CONNECTED) Serial.println("[NET] Wi-Fi connected");
else { Serial.print("[NET] Wi-Fi connection failed (status "); Serial.print(ws); Serial.println("), retrying in 15 s"); }
}
rtos::ThisThread::sleep_for(200ms);
continue;
}
ArtRequest rq;
bool have = false;
netMtx.lock();
if (netReqPending && !netResultReady) { rq = netReq; netReqPending = false; have = true; }
netMtx.unlock();
if (have) {
bool ok = fetchArt(rq);
netMtx.lock();
netResultOk = ok;
netResultSeq = rq.seq;
netResultReady = true;
netMtx.unlock();
}
rtos::ThisThread::sleep_for(50ms);
}
}
// Background color: the cover's average hue, more saturated and darkened so
// white text stays readable
static void applyBgFromAvg(int r, int g, int b) {
float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f;
float mx = max(rf, max(gf, bf)), mn = min(rf, min(gf, bf));
if (mx < 0.03f) { bgR = 28; bgG = 28; bgB = 30; return; }
float d = mx - mn, s = d / mx, h = 0;
if (d > 0.0001f) {
if (mx == rf) h = fmodf((gf - bf) / d, 6.0f);
else if (mx == gf) h = (bf - rf) / d + 2.0f;
else h = (rf - gf) / d + 4.0f;
if (h < 0) h += 6.0f;
}
s = s * 1.8f; if (s > 0.85f) s = 0.85f;
const float v = 0.235f;
float c = v * s, x = c * (1 - fabsf(fmodf(h, 2.0f) - 1)), m = v - c;
float r1 = 0, g1 = 0, b1 = 0;
if (h < 1) { r1 = c; g1 = x; } else if (h < 2) { r1 = x; g1 = c; } else if (h < 3) { g1 = c; b1 = x; }
else if (h < 4) { g1 = x; b1 = c; } else if (h < 5) { r1 = x; b1 = c; } else { r1 = c; b1 = x; }
bgR = (uint8_t)((r1 + m) * 255 + 0.5f); bgG = (uint8_t)((g1 + m) * 255 + 0.5f); bgB = (uint8_t)((b1 + m) * 255 + 0.5f);
}
// Asks the network thread for the current track's cover
static void requestArt() {
char keyBuf[200];
snprintf(keyBuf, sizeof(keyBuf), "%s\x1F%s", amsArtist, amsAlbum[0] ? amsAlbum : amsTitle);
if (strcmp(keyBuf, lastArtKey) == 0) return;
if (strcmp(keyBuf, lastArtKeyTried) != 0) { artRetries = 0; strcpy(lastArtKeyTried, keyBuf); }
artRetryAt = 0;
strcpy(lastArtKey, keyBuf);
coverValid = false;
bgR = 28; bgG = 28; bgB = 30;
needBgRebuild = true;
if (!amsArtist[0] && !amsAlbum[0] && !amsTitle[0]) return;
netMtx.lock();
artSeq++;
strncpy(netReq.artist, amsArtist, sizeof(netReq.artist) - 1); netReq.artist[sizeof(netReq.artist) - 1] = 0;
strncpy(netReq.album, amsAlbum, sizeof(netReq.album) - 1); netReq.album[sizeof(netReq.album) - 1] = 0;
strncpy(netReq.title, amsTitle, sizeof(netReq.title) - 1); netReq.title[sizeof(netReq.title) - 1] = 0;
netReq.seq = artSeq;
netReqPending = true;
netMtx.unlock();
}
static void takeArtResult() {
if (!netResultReady) return;
netMtx.lock();
if (netResultSeq == artSeq && netResultOk) {
memcpy(coverBuf, netCover, COVER_S * COVER_S * 2);
coverValid = true;
applyBgFromAvg(netAvgR, netAvgG, netAvgB);
needBgRebuild = true;
} else if (netResultSeq == artSeq && !netResultOk && artRetries < 3) {
artRetries++;
artRetryAt = millis() + 10000; // retry in 10 s
}
netResultReady = false;
netMtx.unlock();
}
// =====================================================================
// Apple Media Service (AMS)
// =====================================================================
static void copyClean(char* dst, size_t cap, const uint8_t* src, int len) {
int n = 0;
for (int i = 0; i < len && n < (int)cap - 1; i++) {
uint8_t c = src[i];
if (c == 0) break;
dst[n++] = (c < 0x20 || c == 0x7F) ? ' ' : (char)c;
}
// never end on a partial UTF-8 sequence
int k = n;
while (k > 0 && (((uint8_t)dst[k - 1]) & 0xC0) == 0x80) k--;
if (k > 0 && ((uint8_t)dst[k - 1]) >= 0xC0) {
uint8_t lead = dst[k - 1];
int need = (lead >= 0xF0) ? 4 : (lead >= 0xE0) ? 3 : 2;
if (n - (k - 1) < need) n = k - 1;
}
dst[n] = 0;
}
static void onEntityUpdate(BLEDevice dev, BLECharacteristic ch) {
(void)dev;
const uint8_t* v = ch.value();
int n = ch.valueLength();
if (!v || n < 3) return;
uint8_t ent = v[0], attr = v[1];
const uint8_t* s = v + 3;
int sl = n - 3;
if (ent == AMS_ENTITY_TRACK) {
if (attr == AMS_TRACK_TITLE) copyClean(amsTitle, sizeof(amsTitle), s, sl);
else if (attr == AMS_TRACK_ARTIST) copyClean(amsArtist, sizeof(amsArtist), s, sl);
else if (attr == AMS_TRACK_ALBUM) copyClean(amsAlbum, sizeof(amsAlbum), s, sl);
else if (attr == AMS_TRACK_DURATION) {
char t[24]; copyClean(t, sizeof(t), s, sl);
float d = atof(t);
amsDuration = (d > 0 && d < 360000) ? d : 0;
}
amsTrackDirty = true;
amsTrackChangedAt = millis();
} else if (ent == AMS_ENTITY_PLAYER && attr == AMS_PLAYER_PLAYBACKINFO) {
char t[48]; copyClean(t, sizeof(t), s, sl);
char* c1 = strchr(t, ',');
char* c2 = c1 ? strchr(c1 + 1, ',') : nullptr;
amsState = (uint8_t)atoi(t);
amsRate = c1 ? atof(c1 + 1) : 0;
float e = c2 ? atof(c2 + 1) : 0;
amsElapsed = (e >= 0 && e < 360000) ? e : 0;
amsElapsedAt = millis();
}
}
static bool getConnHandle(BLEDevice& dev, uint16_t& handle) {
String a = dev.address();
if (a.length() != 17) return false;
uint8_t addr[6];
for (int i = 0; i < 6; i++) addr[5 - i] = (uint8_t)strtol(a.substring(i * 3, i * 3 + 2).c_str(), nullptr, 16);
for (uint8_t t = 0; t < 4; t++) {
uint16_t h = ATT.connectionHandle(t, addr);
if (h != 0xffff) { handle = h; return true; }
}
return false;
}
static bool isEncrypted(BLEDevice& dev) {
uint16_t h;
if (!getConnHandle(dev, h)) return false;
return (ATT.getPeerEncryption(h) & PEER_ENCRYPTION::ENCRYPTED_AES) != 0;
}
static void sendSecurityRequest(BLEDevice& dev) {
uint16_t h;
if (!getConnHandle(dev, h)) return;
uint8_t req[2] = {CONNECTION_SECURITY_REQUEST, 0x0D}; // bonding + MITM + Secure Connections
HCI.sendAclPkt(h, SECURITY_CID, sizeof(req), req);
}
static void resetAmsData() {
amsReady = false;
releaseAmsChars();
amsTitle[0] = amsArtist[0] = amsAlbum[0] = 0;
amsDuration = amsElapsed = amsRate = 0;
amsState = 0;
lastArtKey[0] = 0;
coverValid = false;
bgR = 28; bgG = 28; bgB = 30;
// Discard any cover lookup still running for the previous connection
netMtx.lock();
artSeq++;
netReqPending = false;
netMtx.unlock();
artRetryAt = 0;
}
static void setIdleTexts() {
strcpy(uiTitle, "Not connected");
snprintf(uiArtist, sizeof(uiArtist), "Settings \xE2\x80\xBA Bluetooth \xE2\x80\xBA %s", DEVICE_NAME);
needBgRebuild = true;
}
static void releaseAmsChars() {
delete chRemote; chRemote = nullptr;
delete chUpdate; chUpdate = nullptr;
}
static bool setupAms(BLEDevice& dev) {
if (!dev.discoverService(AMS_SERVICE)) { Serial.println("[BLE] Apple Media Service not found"); return false; }
releaseAmsChars();
chRemote = new BLECharacteristic(dev.characteristic(AMS_REMOTE));
chUpdate = new BLECharacteristic(dev.characteristic(AMS_UPDATE));
if (!(*chRemote) || !(*chUpdate)) { Serial.println("[BLE] AMS characteristics missing"); releaseAmsChars(); return false; }
chUpdate->setEventHandler(BLEUpdated, onEntityUpdate);
if (!chUpdate->subscribe()) { Serial.println("[BLE] Could not subscribe to AMS"); releaseAmsChars(); return false; }
chRemote->subscribe();
const uint8_t subPlayer[] = {AMS_ENTITY_PLAYER, AMS_PLAYER_PLAYBACKINFO};
const uint8_t subTrack[] = {AMS_ENTITY_TRACK, AMS_TRACK_ARTIST, AMS_TRACK_ALBUM, AMS_TRACK_TITLE, AMS_TRACK_DURATION};
if (!chUpdate->writeValue(subPlayer, sizeof(subPlayer), true)) { Serial.println("[BLE] iPhone rejected the player subscription"); releaseAmsChars(); return false; }
if (!chUpdate->writeValue(subTrack, sizeof(subTrack), true)) { Serial.println("[BLE] iPhone rejected the track subscription"); releaseAmsChars(); return false; }
return true;
}
static void sendToggle() {
if (!amsReady || !chRemote) return;
uint8_t cmd = AMS_CMD_TOGGLE;
if (chRemote->writeValue(&cmd, 1, true)) {
// update the screen right away; the iPhone confirms later
if (amsState == 1) { amsElapsed = amsElapsed + amsRate * (millis() - amsElapsedAt) / 1000.0f; amsState = 0; }
else { amsState = 1; if (amsRate <= 0) amsRate = 1.0f; }
amsElapsedAt = millis();
}
}
static void forgetIphone() {
if (central && central.connected()) central.disconnect();
hasBond = false;
mbedtls_platform_zeroize(&bond, sizeof(bond));
bondDirty = false;
netMtx.lock();
mbedtls_platform_zeroize(&storageRec, sizeof(storageRec));
storageOp = 2; // erased by the network thread
netMtx.unlock();
BLE.setPairable(Pairable::YES);
Serial.println("[BLE] iPhone forgotten");
resetAmsData();
strcpy(uiTitle, "iPhone forgotten");
snprintf(uiArtist, sizeof(uiArtist), "Settings \xE2\x80\xBA Bluetooth \xE2\x80\xBA %s", DEVICE_NAME);
needBgRebuild = true;
}
// ---------- LE Secure Connections: P-256 public key exchange ----------
// On the GIGA, ArduinoBLE skips this step: the controller assigns connection
// handles above 5, which the library treats as invalid. This does what the
// library would do, with the keys computed on the board (mbedTLS).
static int trngRng(void*, unsigned char* out, size_t len) { return randomBytes(out, len) ? 0 : -1; }
static void smpReset() {
smpDone = false;
smpReqSeenAt = 0;
smpLastFlags = 0;
mbedtls_platform_zeroize(smpLocalPk, sizeof(smpLocalPk));
}
static bool smpDoKeyExchange(uint16_t h) {
mbedtls_ecp_group grp;
mbedtls_mpi d, zs;
mbedtls_ecp_point q, qp;
mbedtls_ecp_group_init(&grp);
mbedtls_mpi_init(&d); mbedtls_mpi_init(&zs);
mbedtls_ecp_point_init(&q); mbedtls_ecp_point_init(&qp);
uint8_t x[32], y[32], rx[32], ry[32], z[32];
for (int i = 0; i < 32; i++) { rx[i] = HCI.remotePublicKeyBuffer[31 - i]; ry[i] = HCI.remotePublicKeyBuffer[63 - i]; }
bool ok = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0 &&
mbedtls_mpi_read_binary(&qp.X, rx, 32) == 0 &&
mbedtls_mpi_read_binary(&qp.Y, ry, 32) == 0 &&
mbedtls_mpi_lset(&qp.Z, 1) == 0 &&
mbedtls_ecp_check_pubkey(&grp, &qp) == 0 && // reject invalid iPhone keys
mbedtls_ecp_gen_keypair(&grp, &d, &q, trngRng, nullptr) == 0 &&
mbedtls_mpi_write_binary(&q.X, x, 32) == 0 &&
mbedtls_mpi_write_binary(&q.Y, y, 32) == 0 &&
mbedtls_ecdh_compute_shared(&grp, &zs, &qp, &d, trngRng, nullptr) == 0 &&
mbedtls_mpi_write_binary(&zs, z, 32) == 0;
mbedtls_ecp_point_free(&q); mbedtls_ecp_point_free(&qp);
mbedtls_mpi_free(&d); mbedtls_mpi_free(&zs);
mbedtls_ecp_group_free(&grp);
if (!ok) { mbedtls_platform_zeroize(z, sizeof(z)); return false; }
// 1) Board's public key, X and Y little-endian as SMP requires
struct __attribute__((packed)) { uint8_t code; uint8_t pk[64]; } pub;
pub.code = CONNECTION_PAIRING_PUBLIC_KEY;
for (int i = 0; i < 32; i++) { pub.pk[i] = x[31 - i]; pub.pk[32 + i] = y[31 - i]; }
memcpy(smpLocalPk, pub.pk, 64);
memcpy(HCI.localPublicKeyBuffer, pub.pk, 64);
HCI.sendAclPkt(h, SECURITY_CID, sizeof(pub), &pub);
ATT.setPeerEncryption(h, ATT.getPeerEncryption(h) | PEER_ENCRYPTION::SENT_PUBKEY);
// 2) Random Nb and confirm value Cb = f4(PKb, PKa, Nb, 0), as the library does
if (!randomBytes(HCI.Nb, 16)) return false;
struct __attribute__((packed)) { uint8_t U[32]; uint8_t V[32]; uint8_t Z; } f4 = {{0}, {0}, 0};
for (int i = 0; i < 32; i++) { f4.U[31 - i] = pub.pk[i]; f4.V[31 - i] = HCI.remotePublicKeyBuffer[i]; }
struct __attribute__((packed)) { uint8_t code; uint8_t cb[16]; } conf;
conf.code = CONNECTION_PAIRING_CONFIRM;
uint8_t cb[16];
btct.AES_CMAC(HCI.Nb, (unsigned char*)&f4, sizeof(f4), cb);
for (int i = 0; i < 16; i++) conf.cb[i] = cb[15 - i];
HCI.sendAclPkt(h, SECURITY_CID, sizeof(conf), &conf);
// 3) Shared secret (DHKey), big-endian as the library stores it
memcpy(HCI.DHKey, z, 32);
mbedtls_platform_zeroize(z, sizeof(z));
uint8_t f = ATT.getPeerEncryption(h) | PEER_ENCRYPTION::DH_KEY_CALULATED;
ATT.setPeerEncryption(h, f);
if (f & PEER_ENCRYPTION::RECEIVED_DH_CHECK) L2CAPSignaling.smCalculateLTKandConfirm(h, HCI.remoteDHKeyCheckBuffer);
return true;
}
// Tracks pairing progress, logs each step and runs the key exchange
static void smpWatch(BLEDevice& dev) {
uint16_t h;
if (!getConnHandle(dev, h)) return;
uint8_t f = ATT.getPeerEncryption(h);
uint8_t changed = f ^ smpLastFlags;
smpLastFlags = f;
if (changed & f & PEER_ENCRYPTION::PAIRING_REQUEST) Serial.println("[SMP] 1/5 iPhone requested pairing");
if (changed & f & PEER_ENCRYPTION::REQUESTED_ENCRYPTION) Serial.println("[SMP] 2/5 Received the iPhone's public key");
if (changed & f & PEER_ENCRYPTION::SENT_PUBKEY) Serial.println("[SMP] 3/5 Sent the board's public key");
if (changed & f & PEER_ENCRYPTION::DH_KEY_CALULATED) Serial.println("[SMP] 4/5 Shared secret computed");
if (changed & f & PEER_ENCRYPTION::RECEIVED_DH_CHECK) Serial.println("[SMP] 5/5 Received the DHKey check");
uint32_t now = millis();
if (!smpDone && (f & PEER_ENCRYPTION::REQUESTED_ENCRYPTION) && !(f & PEER_ENCRYPTION::SENT_PUBKEY)) {
if (!smpReqSeenAt) smpReqSeenAt = now;
if (now - smpReqSeenAt > 1500) { // let the controller's (discarded) reply arrive first
smpDone = true;
Serial.println("[SMP] Computing keys on the board");
if (!smpDoKeyExchange(h)) {
Serial.println("[SMP] Invalid iPhone key or key computation failed; aborting");
uint8_t fail[2] = {CONNECTION_PAIRING_FAILED, 0x08}; // unspecified reason
HCI.sendAclPkt(h, SECURITY_CID, sizeof(fail), fail);
}
}
}
// If a late controller reply overwrote our public key, restore it
if (smpDone && (f & PEER_ENCRYPTION::SENT_PUBKEY)) memcpy(HCI.localPublicKeyBuffer, smpLocalPk, 64);
}
static void setStatus(const char* t, const char* a) {
if (strcmp(uiTitle, t) == 0 && strcmp(uiArtist, a) == 0) return;
strncpy(uiTitle, t, sizeof(uiTitle) - 1); uiTitle[sizeof(uiTitle) - 1] = 0;
strncpy(uiArtist, a, sizeof(uiArtist) - 1); uiArtist[sizeof(uiArtist) - 1] = 0;
needBgRebuild = true;
}
static bool pairingInProgress(BLEDevice& dev) {
uint16_t h;
if (!getConnHandle(dev, h)) return false;
return (ATT.getPeerEncryption(h) & PEER_ENCRYPTION::PAIRING_REQUEST) != 0;
}
const char* failTitle = nullptr;
const char* failHint = nullptr;
bool encLogged = false;
static void handleConnection() {
central = BLE.central();
bool conn = central && central.connected();
uint32_t now = millis();
if (conn && !wasConnected) {
wasConnected = true;
connectedAt = now;
secReqSent = false;
amsReady = false;
encLogged = false;
discoverTries = 0;
lastDiscoverTry = 0;
failTitle = failHint = nullptr;
smpReset();
Serial.println("[BLE] iPhone connected");
setStatus("Connecting\xE2\x80\xA6", hasBond ? "Securing the connection" : "Accept on your iPhone");
} else if (!conn && wasConnected) {
wasConnected = false;
smpReset();
Serial.println("[BLE] iPhone disconnected");
resetAmsData();
if (failTitle) setStatus(failTitle, failHint);
else setIdleTexts();
return;
}
if (!conn) return;
if (!amsReady) {
if (!isEncrypted(central)) {
smpWatch(central);
bool pairing = pairingInProgress(central);
if (pairing) setStatus("Pairing\xE2\x80\xA6", "Confirm on both screens");
if (!pairing && !secReqSent && now - connectedAt > 3000) {
Serial.println("[BLE] Asking the iPhone to encrypt the link");
sendSecurityRequest(central);
secReqSent = true;
}
uint32_t limit = hasBond ? ENCRYPT_TIMEOUT_MS : PAIRING_TIMEOUT_MS;
if (now - connectedAt > limit && !pairingShown) {
Serial.println(hasBond ? "[BLE] The iPhone did not use the stored pairing"
: "[BLE] Pairing did not complete");
failTitle = "Couldn't pair";
failHint = hasBond ? "Hold play 5 s to reset" : "Forget " DEVICE_NAME " on iPhone";
central.disconnect();
}
return;
}
if (!encLogged) {
encLogged = true;
smpReset();
Serial.println("[BLE] Link encrypted");
setStatus("Connected", "Loading music\xE2\x80\xA6");
}
if (discoverTries < 3 && now - lastDiscoverTry > 1200) {
lastDiscoverTry = now;
discoverTries++;
Serial.print("[BLE] Discovering AMS, attempt ");
Serial.println(discoverTries);
if (setupAms(central)) {
amsReady = true;
Serial.println("[BLE] AMS ready");
setStatus("Nothing playing", "");
}
} else if (discoverTries >= 3 && now - lastDiscoverTry > 1200) {
failTitle = "Couldn't read music";
failHint = "Reconnect from Settings";
central.disconnect(); // nothing to do without AMS
}
}
}
// =====================================================================
// Setup
// =====================================================================
void setup() {
Serial.begin(115200);
uint32_t t0 = millis();
while (!Serial && millis() - t0 < 1500) {}
SDRAM.begin();
fb = (uint16_t*)SDRAM.malloc(SCR_W * SCR_H * 2);
bgL = (uint16_t*)SDRAM.malloc(SCR_W * SCR_H * 2);
bg8 = (uint8_t*)SDRAM.malloc(SCR_W * SCR_H * 3);
coverBuf = (uint16_t*)SDRAM.malloc(COVER_S * COVER_S * 2);
netCover = (uint16_t*)SDRAM.malloc(COVER_S * COVER_S * 2);
jpgBuf = (uint8_t*)SDRAM.malloc(JPG_MAX);
decBuf = (uint16_t*)SDRAM.malloc(DEC_MAX * DEC_MAX * 2);
if (!fb || !bgL || !bg8 || !coverBuf || !netCover || !jpgBuf || !decBuf) {
Serial.println("Out of SDRAM");
while (true) delay(1000);
}
pinMode(TFT_BL, OUTPUT);
analogWrite(TFT_BL, 0);
if (!tft->begin(SPI_HZ)) Serial.println("Display init failed");
tft->fillScreen(0x0000);
analogWrite(TFT_BL, 255);
pinMode(CTP_RST, OUTPUT);
digitalWrite(CTP_RST, LOW);
delay(10);
digitalWrite(CTP_RST, HIGH);
delay(300);
Wire.begin();
Wire.setClock(400000);
storageInit();
BLE.setGetIRKs(cbGetIRKs);
BLE.setGetLTK(cbGetLTK);
BLE.setStoreIRK(cbStoreIRK);
BLE.setStoreLTK(cbStoreLTK);
BLE.setDisplayCode(cbDisplayCode);
BLE.setBinaryConfirmPairing(cbConfirmPairing);
if (!BLE.begin()) {
Serial.println("Bluetooth init failed");
strcpy(uiTitle, "Bluetooth error");
uiArtist[0] = 0;
} else {
BLE.setDeviceName(DEVICE_NAME);
BLE.setLocalName(DEVICE_NAME);
advData.setRawData(ADV_RAW, sizeof(ADV_RAW));
scanData.setLocalName(DEVICE_NAME);
BLE.setAdvertisingData(advData);
BLE.setScanResponseData(scanData);
BLE.setPairable(hasBond ? Pairable::NO : Pairable::YES);
BLE.advertise();
Serial.println(hasBond ? "[BLE] Advertising as " DEVICE_NAME " (an iPhone is already paired)"
: "[BLE] Advertising as " DEVICE_NAME " (ready to pair)");
}
netThread.start(netTask);
buildBackground();
needBgRebuild = false;
needFull = true;
lastFrame = millis();
}
// =====================================================================
// Loop
// =====================================================================
static void handleTouch() {
int x, y;
bool down = readTouch(x, y);
uint32_t now = millis();
if (down && !touchWasDown) {
touchDownAt = now;
float dx = x - BTN_CX, dy = y - BTN_CY;
touchOnButton = (dx * dx + dy * dy) <= 34.0f * 34.0f;
forgetDone = false;
} else if (down && touchOnButton && !forgetDone && now - touchDownAt > FORGET_HOLD_MS) {
forgetDone = true;
forgetIphone();
} else if (!down && touchWasDown) {
// a short tap toggles play/pause on release, so a long hold never triggers it
if (touchOnButton && !forgetDone && now - touchDownAt < 1000) sendToggle();
touchOnButton = false;
}
touchWasDown = down;
}
static void updateAnimation(uint32_t now, float dt) {
float target = uiPlaying() ? ARM_DOWN : ARM_UP;
if (target != armTo) {
armFrom = armDeg;
armTo = target;
armAnimStart = now;
armAnimating = true;
spinning = false;
}
if (armAnimating) {
float t = (now - armAnimStart) / (float)ARM_ANIM_MS;
armDeg = armFrom + (armTo - armFrom) * easeStandard(t);
if (t >= 1.0f) {
armDeg = armTo;
armAnimating = false;
if (armTo == ARM_DOWN) { spinning = true; wobbleStart = now; }
}
} else if (spinning) {
armDeg = ARM_DOWN + ARM_WOBBLE * sinf((now - wobbleStart) * (6.2831853f / 1800.0f));
}
if (!uiPlaying()) spinning = false;
if (spinning) {
discDeg += dt * DISC_DPS;
if (discDeg >= 360.0f) discDeg -= 360.0f;
}
}
void loop() {
pollBle();
handleConnection();
handleTouch();
if (bondDirty && storageOp == 0) {
bondDirty = false;
netMtx.lock();
storageRec = bond;
storageOp = 1; // written to flash by the network thread
netMtx.unlock();
}
// New track: wait briefly until all of its attributes have arrived
if (amsTrackDirty && millis() - amsTrackChangedAt > 250) {
amsTrackDirty = false;
if (amsReady) {
if (amsTitle[0] || amsArtist[0]) {
strncpy(uiTitle, amsTitle[0] ? amsTitle : "Unknown", sizeof(uiTitle) - 1);
strncpy(uiArtist, amsArtist, sizeof(uiArtist) - 1);
} else {
strcpy(uiTitle, "Nothing playing");
uiArtist[0] = 0;
}
needBgRebuild = true;
requestArt();
}
}
takeArtResult();
if (artRetryAt && (int32_t)(millis() - artRetryAt) >= 0 && amsReady) {
artRetryAt = 0;
lastArtKey[0] = 0;
Serial.println("[NET] Retrying the cover");
requestArt();
}
uint32_t now = millis();
if (now - lastFrame < FRAME_MS) {
rtos::ThisThread::sleep_for(1ms); // idle until the next frame, leaving the CPU to the network thread
return;
}
float dt = (now - lastFrame) / 1000.0f;
if (dt > 0.1f) dt = 0.1f;
lastFrame = now;
if (pairingShown) return;
updateAnimation(now, dt);
float fillW = currentProgress() * BAR_W;
int icon = uiPlaying() ? 1 : 0;
if (needBgRebuild) { buildBackground(); needBgRebuild = false; needFull = true; }
if (needFull) {
needFull = false;
Rect full = {0, 0, SCR_W, SCR_H};
composeRect(full, fillW);
drawnArmDeg = armDeg; drawnDiscDeg = discDeg; drawnBarW = fillW; drawnIcon = icon;
drawnArmRect = armRectFor(armDeg);
return;
}
if (discDeg != drawnDiscDeg) {
composeRect(labelRect(), fillW);
drawnDiscDeg = discDeg;
}
if (armDeg != drawnArmDeg) {
Rect nr = armRectFor(armDeg);
composeRect(rectUnion(drawnArmRect, nr), fillW);
drawnArmRect = nr;
drawnArmDeg = armDeg;
}
if (icon != drawnIcon) {
composeRect(buttonRect(), fillW);
drawnIcon = icon;
}
if (fabsf(fillW - drawnBarW) >= 0.25f) {
composeRect(barRect(), fillW);
drawnBarW = fillW;
}
}
// Template for your private settings.
// Copy this file to arduino_secrets.h and fill in your own values.
// Never publish arduino_secrets.h (it is listed in .gitignore).
#pragma once
// 2.4 GHz Wi-Fi network: the GIGA R1 WiFi cannot join 5 GHz networks.
// If you use the iPhone's Personal Hotspot, turn on "Maximize Compatibility".
#define SECRET_SSID "your-network-name"
#define SECRET_PASS "your-network-password"
// Key that encrypts the pairing stored on the board. Use a long random string
// of at least 32 characters. Pairing is not saved while the value is shorter
// or still contains CHANGE_ME. If you change it later, the board forgets the
// iPhone and you have to pair again.
#define SECRET_STORAGE_KEY "CHANGE_ME_to_a_long_random_string_of_32_or_more_characters"
// Anti-aliased bitmap fonts (4 bits per pixel) rendered from Inter for this display.
// Inter: Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter).
// This file is a Modified Version of Inter and, like Inter, is licensed under the
// SIL Open Font License, Version 1.1. The full license text is in OFL.txt.
#pragma once
#include <stdint.h>
struct VGlyph { uint16_t cp; uint32_t off; uint8_t w, h; int8_t xo, yo; uint16_t adv16; };
struct VFont { const VGlyph* g; uint16_t n; const uint8_t* bmp; uint8_t ascent, descent; };
static const uint8_t F_TITLE_bmp[] = {
255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,0,0,139,16,239,64,
173,16,255,159,240,255,159,240,255,159,240,255,159,240,255,159,240,0,7,245,0,222,0,0,
9,242,0,251,0,0,12,240,3,249,0,13,255,255,255,255,243,13,255,253,223,255,209,0,
79,112,10,241,0,0,127,80,13,224,0,0,159,32,15,176,0,159,255,255,255,255,112,173,
255,221,255,253,64,2,250,0,143,64,0,4,247,0,175,16,0,7,245,0,222,0,0,0,
0,23,0,0,0,0,0,63,0,0,0,0,0,63,0,0,0,0,41,255,235,64,0,3,
239,255,255,246,0,11,252,79,27,254,16,14,245,63,2,187,48,13,251,79,0,0,0,6,
255,255,97,0,0,0,93,255,255,145,0,0,0,95,207,251,0,0,0,63,5,255,48,111,
225,63,1,255,64,63,250,79,26,255,16,9,255,255,255,247,0,0,91,255,234,64,0,0,
0,63,0,0,0,0,0,23,0,0,0,26,238,145,0,0,79,112,0,191,239,248,0,1,
219,0,0,254,18,252,0,10,226,0,0,238,35,252,0,95,80,0,0,175,255,247,2,233,
0,0,0,25,220,112,11,209,0,0,0,0,0,0,127,48,1,16,0,0,0,3,248,2,
207,251,16,0,0,12,192,11,247,159,144,0,0,142,32,15,208,31,192,0,4,246,0,14,
241,63,192,0,29,160,0,10,254,255,128,0,174,16,0,1,174,233,0,0,125,253,128,0,
0,7,255,239,249,0,0,14,248,8,254,0,0,14,246,6,253,0,0,10,252,94,246,0,
0,2,255,255,112,0,0,3,255,250,0,0,0,62,255,255,81,255,64,207,144,175,231,255,
32,255,80,11,255,252,0,207,193,3,255,245,0,79,255,239,255,251,0,3,174,253,149,239,
160,255,64,255,64,255,64,255,64,255,64,1,239,48,8,252,0,13,246,0,47,242,0,111,
208,0,175,160,0,207,128,0,239,96,0,255,80,0,239,80,0,223,112,0,191,128,0,143,
192,0,79,241,0,14,246,0,8,251,0,1,239,32,13,245,0,8,252,0,2,255,32,0,
207,112,0,159,160,0,111,224,0,63,242,0,47,243,0,31,244,0,31,244,0,47,243,0,
79,241,0,143,208,0,191,144,1,255,64,7,252,0,13,245,0,0,3,240,0,7,99,240,
133,10,252,253,231,0,79,254,32,9,252,253,231,7,99,240,133,0,3,240,0,0,0,255,
64,0,0,0,255,64,0,0,0,255,64,0,126,238,255,254,231,143,255,255,255,248,0,0,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,6,253,8,248,10,244,
13,224,15,160,221,221,221,255,255,255,139,0,239,64,173,16,0,0,175,80,0,0,239,16,
0,3,252,0,0,7,248,0,0,11,244,0,0,14,240,0,0,79,176,0,0,143,112,0,
0,207,48,0,1,254,0,0,5,250,0,0,9,246,0,0,12,242,0,0,31,208,0,0,
95,144,0,0,159,80,0,0,0,75,238,180,0,6,255,255,255,96,47,248,0,143,242,143,
192,0,13,248,207,128,0,8,252,239,80,0,5,254,255,80,0,5,255,239,80,0,5,254,
207,128,0,8,252,143,192,0,13,248,47,248,0,143,242,7,255,239,255,112,0,75,238,181,
0,2,191,244,94,255,244,255,111,244,211,15,244,16,15,244,0,15,244,0,15,244,0,15,
244,0,15,244,0,15,244,0,15,244,0,15,244,0,15,244,1,157,254,146,0,46,255,239,
254,48,175,210,2,223,176,239,96,0,111,224,34,16,0,111,224,0,0,1,223,144,0,0,
27,253,16,0,1,191,226,0,0,28,253,32,0,2,223,210,0,0,45,253,32,0,0,239,
254,221,221,208,255,255,255,255,240,2,157,254,181,0,62,255,239,255,128,207,194,0,143,242,
120,48,0,31,244,0,0,1,143,225,0,8,239,251,32,0,9,255,252,48,0,0,3,191,
208,0,0,0,47,243,205,80,0,31,244,191,211,1,175,225,46,255,239,255,80,2,157,254,
163,0,0,0,2,239,244,0,0,0,11,255,244,0,0,0,127,255,244,0,0,3,255,127,
244,0,0,29,250,15,244,0,0,159,209,15,244,0,4,255,48,15,244,0,30,246,0,15,
244,0,127,253,221,223,255,208,143,255,255,255,255,240,0,0,0,15,244,0,0,0,0,15,
244,0,0,0,0,15,244,0,63,255,255,255,144,95,253,221,221,128,111,176,0,0,0,127,
160,0,0,0,159,153,238,180,0,175,255,239,255,64,123,178,2,207,208,0,0,0,63,243,
0,0,0,15,244,205,80,0,63,242,191,211,2,207,192,46,255,239,254,48,2,157,253,146,
0,0,58,239,216,16,5,255,254,255,210,30,248,16,45,250,127,192,0,2,67,191,117,206,
233,16,239,175,254,255,209,255,249,16,95,249,239,192,0,8,253,223,144,0,5,255,159,192,
0,8,253,63,249,0,95,247,7,255,254,255,176,0,91,238,199,0,255,255,255,255,240,221,
221,221,239,240,0,0,0,191,160,0,0,4,255,48,0,0,12,250,0,0,0,79,243,0,
0,0,207,160,0,0,5,255,32,0,0,12,250,0,0,0,95,242,0,0,0,207,144,0,
0,5,255,32,0,0,13,249,0,0,0,0,108,238,198,0,10,255,255,255,160,63,247,0,
127,243,79,241,0,31,243,13,249,17,159,208,1,175,255,250,32,7,239,239,253,48,127,227,
1,191,208,223,96,0,47,243,239,96,0,31,244,191,211,1,175,225,62,255,239,255,96,2,
157,254,180,0,1,140,238,180,0,29,255,238,255,112,159,228,0,127,243,239,96,0,10,249,
239,96,0,10,253,191,228,0,111,254,62,255,239,250,255,2,174,236,70,254,0,0,0,9,
251,173,128,0,30,247,127,244,1,175,225,11,255,239,255,64,0,125,254,163,0,173,16,239,
64,139,0,0,0,0,0,0,0,0,0,139,0,239,64,173,16,10,209,14,244,8,177,0,
0,0,0,0,0,0,0,0,0,13,224,15,176,63,112,95,64,127,0,0,0,0,0,16,
0,0,0,92,128,0,0,109,255,112,0,109,255,215,16,109,255,197,0,0,255,211,0,0,
0,223,251,80,0,0,6,223,253,97,0,0,5,207,254,80,0,0,5,207,128,0,0,0,
4,80,255,255,255,255,221,221,221,221,0,0,0,0,0,0,0,0,221,221,221,221,255,255,
255,255,16,0,0,0,0,249,32,0,0,0,255,249,32,0,0,58,255,250,32,0,0,40,
239,250,32,0,0,24,255,128,0,2,142,255,96,3,175,255,146,0,191,255,146,0,0,254,
129,0,0,0,129,0,0,0,0,3,190,252,112,0,79,255,239,250,0,207,177,7,255,32,
170,48,1,255,64,0,0,6,255,16,0,1,159,247,0,0,11,254,80,0,0,63,244,0,
0,0,77,208,0,0,0,0,0,0,0,0,27,128,0,0,0,79,224,0,0,0,29,160,
0,0,0,0,57,206,254,183,16,0,0,0,26,255,254,239,255,229,0,0,1,223,231,32,
0,74,255,96,0,10,254,32,0,0,0,143,242,0,63,244,4,189,180,221,75,250,0,143,
192,79,255,255,255,69,254,0,207,128,223,177,42,255,66,255,48,239,83,255,48,2,255,64,
255,64,255,84,255,16,0,255,64,255,48,239,83,255,32,2,255,66,255,32,207,128,239,160,
9,255,102,254,0,159,192,111,254,239,191,255,246,0,63,245,6,223,213,26,237,112,0,11,
254,48,0,0,0,0,0,0,1,223,249,48,0,20,48,0,0,0,26,255,255,238,255,160,
0,0,0,0,57,206,254,200,48,0,0,0,0,63,255,32,0,0,0,0,159,255,128,0,
0,0,0,239,175,208,0,0,0,4,254,47,244,0,0,0,10,251,12,249,0,0,0,30,
246,7,254,0,0,0,95,242,2,255,80,0,0,191,176,0,207,160,0,1,255,255,255,255,
241,0,6,255,254,238,255,246,0,12,251,0,0,11,252,0,47,246,0,0,6,255,32,127,
241,0,0,1,255,128,255,255,254,216,16,255,237,223,255,176,255,64,0,127,243,255,64,0,
31,244,255,64,0,143,209,255,237,223,250,32,255,255,255,255,144,255,64,1,94,248,255,64,
0,7,254,255,64,0,6,254,255,64,0,45,252,255,237,222,255,244,255,255,255,234,48,0,
23,206,236,129,0,2,223,254,239,253,32,12,253,64,4,223,192,111,226,0,0,63,244,191,
144,0,0,5,83,239,96,0,0,0,0,255,80,0,0,0,0,239,96,0,0,0,0,191,
144,0,0,5,82,111,226,0,0,63,244,28,253,48,4,223,192,2,223,254,239,253,32,0,
23,206,236,113,0,255,255,254,182,16,0,255,253,239,255,194,0,255,64,0,77,252,0,255,
64,0,2,239,96,255,64,0,0,159,176,255,64,0,0,111,208,255,64,0,0,95,240,255,
64,0,0,111,208,255,64,0,0,159,176,255,64,0,2,239,96,255,64,0,93,252,0,255,
253,239,255,194,0,255,255,254,182,0,0,255,255,255,255,128,255,237,221,221,112,255,64,0,
0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,0,255,255,255,255,0,255,64,
0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,112,255,
255,255,255,128,255,255,255,255,128,255,237,221,221,112,255,64,0,0,0,255,64,0,0,0,
255,64,0,0,0,255,64,0,0,0,255,255,255,255,0,255,237,221,221,0,255,64,0,0,
0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,0,6,206,
253,147,0,2,207,255,239,255,96,12,254,80,2,175,244,95,242,0,0,12,251,191,160,0,
0,1,17,223,96,0,0,0,0,255,80,0,127,255,255,223,96,0,109,222,255,191,144,0,
0,6,253,111,226,0,0,11,250,12,253,80,1,159,243,2,207,255,239,255,80,0,6,206,
253,146,0,255,64,0,0,79,240,255,64,0,0,79,240,255,64,0,0,79,240,255,64,0,
0,79,240,255,64,0,0,79,240,255,237,221,221,239,240,255,255,255,255,255,240,255,64,0,
0,79,240,255,64,0,0,79,240,255,64,0,0,79,240,255,64,0,0,79,240,255,64,0,
0,79,240,255,64,0,0,79,240,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,
64,255,64,255,64,255,64,255,64,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,
0,255,64,187,48,0,255,64,239,80,1,255,64,207,177,8,255,16,79,255,239,248,0,4,
190,252,96,0,255,64,0,9,255,80,255,64,0,143,246,0,255,64,6,255,128,0,255,64,
79,249,0,0,255,67,239,176,0,0,255,93,255,16,0,0,255,239,255,80,0,0,255,252,
191,225,0,0,255,241,30,251,0,0,255,96,5,255,96,0,255,64,0,175,226,0,255,64,
0,29,252,0,255,64,0,4,255,112,255,64,0,0,255,64,0,0,255,64,0,0,255,64,
0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,
0,0,255,80,0,0,255,255,255,255,255,255,255,255,255,251,0,0,0,191,255,255,255,32,
0,1,255,255,255,223,112,0,7,253,255,255,159,208,0,12,248,255,255,94,244,0,63,229,
255,255,74,249,0,143,164,255,255,69,254,16,239,84,255,255,64,239,68,254,4,255,255,64,
159,152,249,4,255,255,64,79,220,244,4,255,255,64,13,255,208,4,255,255,64,8,255,128,
4,255,255,64,2,255,48,4,255,255,209,0,0,79,240,255,249,0,0,79,240,255,255,48,
0,79,240,255,175,208,0,79,240,255,92,247,0,79,240,255,68,255,32,79,240,255,64,175,
176,79,240,255,64,46,246,79,240,255,64,7,254,95,240,255,64,0,207,207,240,255,64,0,
63,255,240,255,64,0,8,255,240,255,64,0,1,223,240,0,23,206,236,113,0,2,223,254,
239,253,32,12,253,64,4,223,192,111,226,0,0,46,246,191,144,0,0,9,251,239,96,0,
0,6,253,255,80,0,0,5,255,239,96,0,0,6,253,191,144,0,0,9,251,111,226,0,
0,46,246,12,253,64,4,223,192,2,223,254,239,253,32,0,23,206,236,113,0,255,255,254,
180,0,255,237,239,255,96,255,64,1,159,225,255,64,0,31,244,255,64,0,31,244,255,64,
1,175,225,255,253,239,255,96,255,255,254,181,0,255,64,0,0,0,255,64,0,0,0,255,
64,0,0,0,255,64,0,0,0,255,64,0,0,0,0,23,206,236,113,0,2,223,254,239,
253,32,12,253,64,3,223,192,111,226,0,0,46,246,191,144,0,0,9,251,239,96,0,0,
6,253,255,80,0,0,5,255,239,96,0,0,6,253,191,160,5,132,10,251,111,244,1,238,
111,246,12,254,115,127,255,192,2,223,255,255,255,32,0,23,206,236,255,112,0,0,0,0,
29,244,255,255,254,181,0,255,237,239,255,96,255,64,1,159,225,255,64,0,31,244,255,64,
0,31,244,255,64,1,159,225,255,253,239,255,112,255,255,255,245,0,255,64,47,246,0,255,
64,9,253,0,255,64,1,255,112,255,64,0,159,225,255,64,0,31,247,0,41,223,235,64,
0,3,239,254,255,246,0,11,252,32,27,254,16,14,245,0,2,187,48,13,251,16,0,0,
0,6,255,234,97,0,0,0,93,255,255,145,0,0,0,55,207,251,0,0,0,0,5,255,
48,111,225,0,1,255,64,63,250,16,26,255,16,9,255,254,255,247,0,0,91,239,234,64,
0,255,255,255,255,255,221,221,255,237,221,0,0,255,64,0,0,0,255,64,0,0,0,255,
64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,255,64,0,0,255,64,
255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,
255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,80,0,0,255,64,
223,128,0,4,255,32,127,246,0,61,251,0,11,255,238,255,210,0,0,108,238,216,16,0,
127,242,0,0,2,255,96,31,247,0,0,7,255,16,11,252,0,0,12,250,0,6,255,32,
0,47,245,0,1,239,112,0,127,225,0,0,175,192,0,207,160,0,0,79,242,2,255,64,
0,0,14,247,7,254,0,0,0,9,252,11,249,0,0,0,3,255,47,243,0,0,0,0,
223,175,208,0,0,0,0,143,255,128,0,0,0,0,47,255,32,0,0,127,240,0,4,255,
112,0,12,251,63,244,0,8,255,176,0,31,247,14,247,0,12,255,224,0,95,242,10,251,
0,31,236,243,0,143,208,6,254,0,79,201,247,0,207,144,2,255,48,143,133,251,1,255,
80,0,223,112,207,82,255,4,255,16,0,159,177,255,16,223,72,252,0,0,95,228,252,0,
159,123,248,0,0,31,249,248,0,95,174,244,0,0,12,254,244,0,31,239,224,0,0,8,
255,224,0,12,255,176,0,0,3,255,176,0,8,255,96,0,9,254,32,0,9,254,32,1,
223,176,0,79,245,0,0,79,247,1,239,160,0,0,8,254,41,254,16,0,0,0,223,207,
244,0,0,0,0,63,255,144,0,0,0,0,15,255,64,0,0,0,0,127,255,209,0,0,
0,3,255,126,249,0,0,0,12,252,6,255,80,0,0,143,226,0,191,225,0,4,255,96,
0,46,250,0,29,251,0,0,6,255,96,143,243,0,0,13,252,0,29,251,0,0,127,243,
0,5,255,80,1,239,144,0,0,191,208,9,254,16,0,0,47,247,47,246,0,0,0,7,
254,175,192,0,0,0,0,223,255,48,0,0,0,0,79,249,0,0,0,0,0,15,244,0,
0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,0,0,
0,15,244,0,0,0,255,255,255,255,255,221,221,221,223,254,0,0,0,95,244,0,0,2,
239,128,0,0,12,252,0,0,0,159,226,0,0,5,255,80,0,0,46,249,0,0,0,207,
192,0,0,8,254,32,0,0,79,245,0,0,0,239,253,221,221,221,255,255,255,255,255,221,
221,255,255,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,
64,255,64,255,64,255,237,255,255,159,80,0,0,95,144,0,0,31,208,0,0,12,242,0,
0,9,246,0,0,5,250,0,0,1,254,0,0,0,207,48,0,0,143,112,0,0,79,176,
0,0,14,240,0,0,11,244,0,0,7,248,0,0,3,252,0,0,0,239,16,0,0,175,
80,221,221,64,255,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,
64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,221,255,
64,255,255,64,0,0,0,0,0,30,248,0,0,111,254,16,0,221,111,112,5,247,14,208,
12,241,7,245,61,128,1,218,221,221,221,221,255,255,255,255,125,128,29,225,4,247,2,174,
253,113,0,46,255,239,251,0,126,210,3,255,48,0,0,0,223,64,2,138,206,255,64,95,
251,133,255,64,239,112,1,255,64,239,128,25,255,64,175,254,252,239,64,25,238,162,239,64,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,87,238,179,0,255,191,239,255,
64,255,228,1,207,192,255,128,0,79,242,255,80,0,31,244,255,80,0,31,244,255,128,0,
79,242,255,228,2,207,192,255,191,239,255,64,255,71,238,180,0,0,125,237,146,0,12,255,
239,253,32,127,228,2,207,144,207,128,0,54,48,239,80,0,0,0,239,80,0,0,0,207,
128,0,55,64,127,228,1,207,144,12,255,239,253,32,0,125,253,146,0,0,0,0,15,244,
0,0,0,15,244,0,0,0,15,244,1,158,234,47,244,29,255,239,207,244,143,228,2,207,
244,207,128,0,79,244,239,80,0,31,244,239,80,0,31,244,223,128,0,79,244,143,228,2,
207,244,29,255,239,191,244,2,174,234,31,244,0,141,253,129,0,12,255,239,252,0,127,227,
2,223,112,207,112,0,127,192,239,255,255,255,224,239,253,221,221,208,207,112,0,0,0,127,
227,1,173,128,12,255,239,255,48,1,141,254,163,0,0,2,119,64,0,95,255,160,0,223,
198,48,0,255,80,0,255,255,255,128,221,255,253,112,0,255,64,0,0,255,64,0,0,255,
64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,1,158,
234,31,244,29,255,239,207,244,127,228,1,207,244,207,128,0,79,244,239,80,0,31,244,239,
80,0,31,244,223,128,0,79,244,143,227,1,207,244,29,255,239,191,244,2,174,234,31,244,
0,0,0,31,243,92,194,1,159,225,46,255,239,255,112,2,157,254,181,0,255,64,0,0,
0,255,64,0,0,0,255,64,0,0,0,255,71,222,161,0,255,175,239,251,0,255,241,8,
255,32,255,112,1,255,64,255,80,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,
0,255,64,255,64,0,255,64,255,64,0,255,64,139,0,239,64,173,16,0,0,255,64,255,
64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,0,139,0,0,239,64,0,
173,16,0,0,0,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,
255,64,0,255,64,0,255,64,0,255,64,0,255,64,3,255,48,127,253,0,142,179,0,255,
64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,7,255,64,255,64,95,245,0,
255,68,255,112,0,255,126,249,0,0,255,255,242,0,0,255,255,251,0,0,255,148,255,96,
0,255,64,159,243,0,255,64,29,252,0,255,64,3,255,128,255,64,255,64,255,64,255,64,
255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,90,238,112,76,253,
80,255,207,239,247,254,255,243,255,193,10,255,128,46,249,255,96,5,255,16,9,250,255,64,
4,255,0,9,251,255,64,4,255,0,9,251,255,64,4,255,0,9,251,255,64,4,255,0,
9,251,255,64,4,255,0,9,251,255,64,4,255,0,9,251,255,106,238,161,0,255,255,239,
251,0,255,209,8,255,32,255,112,1,255,64,255,80,0,255,64,255,64,0,255,64,255,64,
0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,0,125,253,146,0,12,
255,239,254,32,127,228,2,207,176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,
207,128,0,79,242,127,228,2,207,176,12,255,239,254,32,0,125,254,146,0,255,87,238,180,
0,255,191,239,255,64,255,228,1,207,192,255,128,0,79,242,255,80,0,31,244,255,80,0,
31,244,255,128,0,79,242,255,228,2,207,192,255,207,239,255,48,255,88,238,179,0,255,64,
0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,1,174,234,30,244,29,
255,239,191,244,143,228,2,207,244,223,128,0,79,244,239,80,0,31,244,239,80,0,31,244,
207,128,0,79,244,143,228,2,207,244,29,255,239,191,244,1,174,234,31,244,0,0,0,15,
244,0,0,0,15,244,0,0,0,15,244,0,0,0,15,244,0,0,0,255,90,248,255,223,
231,255,210,0,255,96,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,
0,0,124,254,180,0,8,255,238,255,48,14,248,0,159,128,13,248,0,0,0,5,255,235,
114,0,0,39,190,255,96,1,32,0,143,224,47,245,0,159,224,9,255,238,255,112,0,124,
254,197,0,0,136,32,0,0,255,64,0,0,255,64,0,143,255,255,0,125,255,253,0,0,
255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,239,96,0,0,
191,254,0,0,43,238,32,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,
0,255,64,255,64,0,255,64,255,64,0,255,64,255,96,2,255,64,223,193,9,255,64,127,
255,236,255,64,7,238,162,255,64,143,208,0,7,253,0,47,243,0,12,248,0,12,248,0,
47,243,0,7,253,0,127,192,0,1,255,48,207,112,0,0,191,130,255,16,0,0,95,198,
251,0,0,0,30,251,245,0,0,0,9,255,225,0,0,0,4,255,160,0,0,143,208,0,
191,160,0,239,112,79,241,0,239,224,3,255,32,14,245,3,255,242,7,253,0,10,249,7,
250,246,10,249,0,5,252,11,243,250,14,244,0,1,255,30,192,221,47,224,0,0,207,127,
128,159,127,176,0,0,127,223,64,95,223,96,0,0,63,254,0,31,255,32,0,0,13,251,
0,12,253,0,0,79,243,0,159,192,10,251,3,255,48,1,239,58,248,0,0,111,223,209,
0,0,15,255,64,0,0,31,255,96,0,0,159,175,226,0,3,255,25,250,0,12,248,1,
239,80,127,225,0,127,225,143,208,0,7,254,0,47,243,0,12,248,0,12,248,0,47,243,
0,6,253,0,127,192,0,1,255,48,207,112,0,0,191,130,255,16,0,0,95,198,251,0,
0,0,14,252,245,0,0,0,9,255,225,0,0,0,4,255,144,0,0,0,2,255,64,0,
0,0,8,253,0,0,0,9,255,245,0,0,0,11,253,112,0,0,0,255,255,255,255,221,
221,223,254,0,0,95,244,0,2,239,128,0,28,252,0,0,175,226,0,6,255,64,0,63,
248,0,0,223,254,221,221,255,255,255,255,0,1,87,0,62,255,0,191,215,0,239,96,0,
255,64,0,255,64,1,255,64,24,253,16,255,241,0,255,252,0,7,255,48,1,255,64,0,
255,64,0,255,80,0,239,160,0,159,254,0,8,222,255,64,255,64,255,64,255,64,255,64,
255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,
255,64,255,64,255,64,255,64,255,64,117,32,0,0,255,246,0,0,124,255,16,0,2,255,
64,0,0,255,64,0,0,255,64,0,0,239,80,0,0,159,178,0,0,7,255,160,0,143,
255,128,0,239,160,0,0,255,80,0,0,255,64,0,1,255,64,0,6,255,48,0,239,253,
0,0,237,146,0,0,25,218,16,8,130,159,255,211,79,243,223,145,191,255,208,170,48,9,
237,48,173,16,239,64,139,16,0,0,0,0,255,64,255,64,255,64,255,64,255,64,255,64,
255,64,255,64,255,64,0,2,224,0,0,0,109,251,96,0,9,255,255,250,0,95,247,244,
255,80,191,162,224,138,80,239,114,224,0,0,255,82,224,0,0,239,114,224,0,0,191,162,
224,139,80,95,247,244,255,80,9,255,255,250,0,0,109,251,96,0,0,2,224,0,0,0,
2,174,253,112,0,0,46,255,239,250,0,0,175,193,4,255,64,0,239,96,0,85,32,0,
255,64,0,0,0,125,255,253,221,0,0,143,255,255,255,0,0,0,255,64,0,0,0,0,
255,64,0,0,0,0,255,64,0,17,16,6,251,0,0,175,112,191,255,255,255,255,64,143,
255,255,255,215,0,0,16,0,0,0,16,0,9,179,157,253,130,200,0,29,255,255,239,255,
252,0,4,255,113,1,159,242,0,9,247,0,0,9,247,0,13,241,0,0,3,251,0,15,
208,0,0,0,252,0,13,241,0,0,3,251,0,9,247,0,0,10,247,0,4,255,113,1,
159,242,0,30,255,255,239,255,253,0,5,82,174,253,129,100,0,111,225,0,10,251,13,247,
0,47,244,6,253,0,159,192,0,239,81,239,64,0,127,199,252,0,0,30,255,245,0,12,
255,255,255,240,8,171,255,250,160,0,0,255,64,0,12,255,255,255,240,11,221,255,253,208,
0,0,255,64,0,0,0,255,64,0,255,64,255,64,255,64,255,64,255,64,255,64,0,0,
0,0,0,0,0,0,255,64,255,64,255,64,255,64,255,64,255,64,0,125,237,129,0,8,
255,239,250,0,14,248,4,255,32,14,246,0,66,0,8,254,113,0,0,3,223,254,129,0,
46,163,175,253,32,127,80,3,223,192,79,194,0,111,224,10,254,130,159,128,0,93,255,212,
0,0,0,93,251,0,2,64,2,255,48,47,244,4,255,48,10,255,239,252,0,0,141,254,
145,0,172,32,172,32,190,32,190,32,0,0,107,221,182,0,0,0,77,255,255,255,212,0,
4,255,196,17,76,255,64,30,250,58,220,96,175,225,127,229,255,255,249,30,247,207,158,251,
18,191,56,252,239,159,242,0,36,37,254,239,159,241,0,19,21,254,223,142,250,1,207,40,
252,143,214,255,239,249,13,248,46,249,92,253,128,159,225,5,255,163,0,58,255,80,0,94,
255,238,255,229,0,0,1,140,238,200,16,0,7,222,217,16,143,166,159,160,1,2,95,208,
59,254,207,208,222,48,31,208,239,134,191,208,60,237,110,208,0,4,255,69,255,48,0,46,
246,62,245,0,1,223,130,239,112,0,28,250,28,250,0,0,28,250,28,250,0,0,1,223,
130,239,112,0,0,46,246,62,245,0,0,4,255,69,255,48,221,221,221,221,64,255,255,255,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,136,32,0,3,173,201,32,0,0,
127,255,255,228,0,4,252,65,21,222,32,12,179,255,229,29,144,31,51,240,109,6,208,47,
3,240,93,3,224,31,35,255,249,5,208,12,163,240,125,28,144,5,250,48,4,207,32,0,
143,254,239,245,0,0,5,190,235,48,0,221,221,208,255,255,240,8,238,128,143,255,248,238,
34,238,238,17,238,143,255,248,8,238,128,0,15,244,0,0,0,15,244,0,0,221,223,255,
221,208,255,255,255,255,240,0,15,244,0,0,0,15,244,0,0,0,12,196,0,0,0,0,
0,0,0,255,255,255,255,240,221,221,221,221,208,43,238,178,206,120,252,67,1,238,0,44,
247,4,238,80,143,231,85,255,255,255,6,223,234,32,95,198,159,176,17,17,95,176,0,79,
251,16,0,20,127,160,95,182,143,176,7,206,218,32,8,215,30,209,127,64,255,64,0,255,
64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,
255,64,255,96,1,255,64,255,241,9,255,96,255,255,239,255,246,255,141,232,76,247,255,64,
0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,2,157,255,255,240,46,
255,253,255,240,175,255,240,79,240,239,255,240,79,240,239,255,240,79,240,207,255,240,79,240,
95,255,240,79,240,5,223,240,79,240,0,2,48,79,240,0,0,0,79,240,0,0,0,79,
240,0,0,0,79,240,0,0,0,79,240,122,0,239,64,173,16,15,0,46,194,0,199,142,
227,17,0,60,253,252,253,80,253,0,253,0,253,0,253,0,253,5,206,235,64,95,214,126,
243,223,32,4,251,254,0,1,253,223,32,4,251,95,214,110,244,5,206,235,64,29,248,46,
247,0,0,3,239,99,255,80,0,0,79,244,95,227,0,0,6,254,55,254,32,0,6,254,
55,254,32,0,79,244,95,227,0,3,239,99,255,80,0,29,248,46,247,0,0,26,253,0,
0,3,252,0,124,253,0,0,12,226,0,65,253,0,0,127,96,0,0,253,0,3,251,0,
0,0,253,0,12,226,0,0,0,253,0,127,96,0,0,0,253,3,250,0,175,240,0,0,
12,225,4,255,240,0,0,143,80,29,173,240,0,3,250,0,157,29,240,0,12,209,1,254,
223,251,0,143,64,1,255,255,253,3,249,0,0,0,13,240,1,32,0,0,0,2,32,6,
169,0,0,2,164,0,110,253,0,0,12,192,0,82,253,0,0,127,32,0,0,253,0,3,
247,0,0,0,253,0,12,192,0,0,0,253,0,143,32,2,0,0,253,3,247,4,239,230,
0,84,29,192,13,183,254,0,0,143,32,2,17,235,0,4,247,0,0,28,209,0,29,192,
0,2,219,16,0,159,32,0,14,254,221,4,247,0,0,15,255,255,7,219,16,0,0,29,
176,0,95,255,176,0,0,159,48,0,140,37,224,0,4,249,0,0,0,6,192,0,13,209,
0,0,0,255,113,0,143,64,0,0,0,6,253,3,249,0,0,0,138,22,255,28,225,0,
0,0,78,255,248,111,80,9,255,0,1,69,34,234,0,63,255,0,0,0,10,225,0,202,
223,0,0,0,95,80,6,225,223,0,0,1,219,0,13,237,255,176,0,9,226,0,13,255,
255,208,0,63,96,0,0,0,223,0,0,18,0,0,0,0,34,0,0,10,209,0,0,0,
14,244,0,0,0,8,177,0,0,0,0,0,0,0,0,12,195,0,0,0,47,244,0,0,
1,191,241,0,0,28,255,96,0,0,143,244,0,0,0,239,112,0,0,0,255,80,1,238,
48,207,177,9,255,16,79,255,255,247,0,4,190,236,96,0,0,0,223,80,0,0,0,0,
0,63,192,0,0,0,0,0,7,210,0,0,0,0,0,0,0,0,0,0,0,0,63,255,
32,0,0,0,0,159,255,128,0,0,0,0,239,175,208,0,0,0,4,254,47,244,0,0,
0,10,251,12,249,0,0,0,30,246,7,254,0,0,0,95,242,2,255,80,0,0,191,176,
0,207,160,0,1,255,255,255,255,241,0,6,255,254,238,255,246,0,12,251,0,0,11,252,
0,47,246,0,0,6,255,32,127,241,0,0,1,255,128,0,0,0,95,192,0,0,0,0,
0,207,48,0,0,0,0,3,215,0,0,0,0,0,0,0,0,0,0,0,0,63,255,32,
0,0,0,0,159,255,128,0,0,0,0,239,175,208,0,0,0,4,254,47,244,0,0,0,
10,251,12,249,0,0,0,30,246,7,254,0,0,0,95,242,2,255,80,0,0,191,176,0,
207,160,0,1,255,255,255,255,241,0,6,255,254,238,255,246,0,12,251,0,0,11,252,0,
47,246,0,0,6,255,32,127,241,0,0,1,255,128,0,0,5,133,0,0,0,0,0,79,
239,64,0,0,0,2,237,45,226,0,0,0,5,131,3,133,0,0,0,0,0,0,0,0,
0,0,0,63,255,32,0,0,0,0,159,255,128,0,0,0,0,239,175,208,0,0,0,4,
254,47,244,0,0,0,10,251,12,249,0,0,0,30,246,7,254,0,0,0,95,242,2,255,
80,0,0,191,176,0,207,160,0,1,255,255,255,255,241,0,6,255,254,238,255,246,0,12,
251,0,0,11,252,0,47,246,0,0,6,255,32,127,241,0,0,1,255,128,0,0,2,0,
33,0,0,0,1,223,182,245,0,0,0,5,246,207,209,0,0,0,1,32,2,16,0,0,
0,0,0,0,0,0,0,0,0,63,255,32,0,0,0,0,159,255,128,0,0,0,0,239,
159,208,0,0,0,4,254,47,244,0,0,0,10,251,12,249,0,0,0,30,246,7,254,0,
0,0,95,242,2,255,80,0,0,191,176,0,207,160,0,1,255,255,255,255,241,0,6,255,
254,238,255,246,0,12,251,0,0,11,252,0,47,246,0,0,6,255,32,127,241,0,0,1,
255,128,0,2,202,10,193,0,0,0,2,235,11,226,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,14,253,0,0,0,0,0,95,255,64,0,0,0,0,191,143,
160,0,0,0,2,254,31,241,0,0,0,7,250,11,247,0,0,0,13,246,6,252,0,0,
0,63,241,2,255,48,0,0,159,176,0,191,144,0,1,239,255,255,255,224,0,5,255,254,
238,255,245,0,11,251,0,0,11,251,0,47,246,0,0,6,255,32,127,241,0,0,1,255,
112,0,0,77,252,48,0,0,0,1,244,7,208,0,0,0,1,244,7,208,0,0,0,0,
77,252,48,0,0,0,0,0,0,0,0,0,0,0,159,255,96,0,0,0,0,239,255,176,
0,0,0,4,255,207,241,0,0,0,9,254,79,246,0,0,0,13,250,14,251,0,0,0,
63,246,9,255,32,0,0,143,241,4,255,112,0,0,223,176,0,223,192,0,3,255,255,255,
255,242,0,8,255,254,238,255,247,0,13,251,0,0,12,252,0,63,246,0,0,6,255,48,
143,241,0,0,1,255,128,0,0,0,223,255,255,255,255,248,0,0,4,255,175,255,255,255,
248,0,0,11,251,15,245,0,0,0,0,0,63,244,15,244,0,0,0,0,0,175,208,15,
244,0,0,0,0,2,255,96,15,254,221,221,208,0,9,254,0,15,255,255,255,240,0,30,
247,0,15,244,0,0,0,0,127,255,255,255,244,0,0,0,0,239,253,221,223,244,0,0,
0,6,255,32,0,15,244,0,0,0,13,250,0,0,15,254,221,221,215,95,244,0,0,15,
255,255,255,248,0,23,206,236,113,0,2,223,254,255,253,32,12,253,64,4,239,176,111,226,
0,0,79,243,191,144,0,0,5,82,239,96,0,0,0,0,255,80,0,0,0,0,239,96,
0,0,0,0,191,144,0,0,5,82,111,226,0,0,79,243,28,253,48,4,239,176,2,223,
254,239,252,16,0,23,207,252,113,0,0,0,9,179,0,0,0,0,7,190,0,0,0,0,
30,250,0,0,0,0,2,16,0,0,1,239,16,0,0,0,111,128,0,0,0,10,176,0,
0,0,0,0,0,0,255,255,255,255,128,255,237,221,221,112,255,64,0,0,0,255,64,0,
0,0,255,64,0,0,0,255,237,221,221,0,255,255,255,255,0,255,64,0,0,0,255,64,
0,0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,112,255,255,255,255,128,0,
0,159,112,0,0,1,237,0,0,0,5,211,0,0,0,0,0,0,0,255,255,255,255,128,
255,237,221,221,112,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,
0,255,255,255,255,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,
0,0,255,237,221,221,112,255,255,255,255,128,0,6,130,0,0,0,127,237,16,0,4,251,
79,160,0,6,129,5,130,0,0,0,0,0,0,255,255,255,255,128,255,237,221,221,112,255,
64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,0,255,255,255,255,0,
255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,255,237,221,221,
112,255,255,255,255,128,10,193,10,194,0,11,226,11,226,0,0,0,0,0,0,0,0,0,
0,0,255,255,255,255,128,255,237,221,221,112,255,64,0,0,0,255,64,0,0,0,255,64,
0,0,0,255,237,221,221,0,255,255,255,255,0,255,64,0,0,0,255,64,0,0,0,255,
64,0,0,0,255,64,0,0,0,255,237,221,221,112,255,255,255,255,128,47,225,0,7,246,
0,0,186,0,0,0,0,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,
64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,10,246,
47,192,125,32,0,0,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,64,
255,64,255,64,255,64,255,64,0,7,130,0,0,143,236,0,5,249,95,144,7,113,6,113,
0,0,0,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,
0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,
0,15,244,0,0,15,244,0,172,16,172,16,190,32,190,32,0,0,0,0,0,0,0,0,
0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,
0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,
0,255,64,0,0,255,255,254,182,16,0,0,255,253,239,255,194,0,0,255,64,0,77,252,
0,0,255,64,0,2,239,96,0,255,64,0,0,159,176,125,255,253,214,0,111,208,143,255,
255,247,0,95,240,0,255,64,0,0,111,208,0,255,64,0,0,159,176,0,255,64,0,2,
239,96,0,255,64,0,93,252,0,0,255,253,239,255,194,0,0,255,255,254,182,0,0,0,
0,32,2,16,0,0,13,252,111,80,0,0,79,108,253,16,0,0,18,0,33,0,0,0,
0,0,0,0,0,255,209,0,0,79,240,255,249,0,0,79,240,255,255,48,0,79,240,255,
175,208,0,79,240,255,92,247,0,79,240,255,68,255,32,79,240,255,64,175,176,79,240,255,
64,46,246,79,240,255,64,7,254,95,240,255,64,0,207,207,240,255,64,0,63,255,240,255,
64,0,8,255,240,255,64,0,1,223,240,0,5,218,0,0,0,0,0,207,48,0,0,0,
0,47,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,206,236,113,0,2,
223,254,239,253,32,12,253,64,4,223,192,111,226,0,0,46,246,191,144,0,0,9,251,239,
96,0,0,6,253,255,80,0,0,5,255,239,96,0,0,6,253,191,144,0,0,9,251,111,
226,0,0,46,246,12,253,64,4,223,192,2,223,254,239,253,32,0,23,206,236,113,0,0,
0,0,173,80,0,0,0,3,251,0,0,0,0,10,226,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,23,206,236,113,0,2,223,254,239,253,32,12,253,64,4,223,192,111,
226,0,0,46,246,191,144,0,0,9,251,239,96,0,0,6,253,255,80,0,0,5,255,239,
96,0,0,6,253,191,144,0,0,9,251,111,226,0,0,46,246,12,253,64,4,223,192,2,
223,254,239,253,32,0,23,206,236,113,0,0,0,23,113,0,0,0,1,206,236,16,0,0,
28,227,62,193,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,206,236,113,0,2,
223,254,239,253,32,12,253,64,4,223,192,111,226,0,0,46,246,191,144,0,0,9,251,239,
96,0,0,6,253,255,80,0,0,5,255,239,96,0,0,6,253,191,144,0,0,9,251,111,
226,0,0,46,246,12,253,64,4,223,192,2,223,254,239,253,32,0,23,206,236,113,0,0,
0,33,1,32,0,0,6,254,122,192,0,0,12,184,239,96,0,0,2,16,18,0,0,0,
0,0,0,0,0,0,23,206,236,113,0,2,223,254,239,253,32,12,253,64,4,223,192,111,
226,0,0,46,246,191,144,0,0,9,251,239,96,0,0,6,253,255,80,0,0,5,255,239,
96,0,0,6,253,191,144,0,0,9,251,111,226,0,0,46,246,12,253,64,4,223,192,2,
223,254,239,253,32,0,23,206,236,113,0,0,11,226,46,176,0,0,10,194,44,160,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,23,206,236,113,0,2,223,254,239,253,32,12,
253,64,4,223,192,111,226,0,0,46,246,191,144,0,0,9,251,239,96,0,0,6,253,255,
80,0,0,5,255,239,96,0,0,6,253,191,144,0,0,9,251,111,226,0,0,46,246,12,
253,64,4,223,192,2,223,254,239,253,32,0,23,206,236,113,0,5,64,0,4,80,63,244,
0,78,243,9,255,68,239,144,0,159,255,250,0,0,13,255,224,0,0,62,255,244,0,3,
239,170,254,48,46,250,0,175,227,11,176,0,10,177,0,0,0,0,16,0,0,0,0,0,
32,0,23,206,236,119,243,2,223,254,239,255,208,12,253,64,4,255,224,111,226,0,10,255,
246,191,144,0,127,121,251,239,96,3,251,6,253,255,80,29,225,5,255,239,96,191,64,6,
253,191,151,247,0,9,251,111,255,176,0,46,246,14,255,80,4,223,192,13,255,254,239,253,
32,63,135,206,236,113,0,2,0,0,0,0,0,0,44,192,0,0,0,0,8,245,0,0,
0,0,1,220,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,80,0,0,255,
64,223,128,0,4,255,32,127,246,0,61,251,0,11,255,238,255,210,0,0,108,238,216,16,
0,0,0,8,213,0,0,0,0,31,192,0,0,0,0,127,48,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,64,0,0,255,64,255,80,0,0,255,64,223,128,0,4,255,32,127,246,0,61,251,
0,11,255,238,255,210,0,0,108,238,216,16,0,0,0,120,32,0,0,0,10,253,209,0,
0,0,159,82,236,16,0,0,0,0,0,0,0,0,0,0,0,0,0,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,80,0,1,255,
64,223,160,0,5,255,32,127,249,50,126,251,0,11,255,255,255,210,0,0,108,238,216,16,
0,0,190,32,190,32,0,0,172,32,172,32,0,0,0,0,0,0,0,0,0,0,0,0,
0,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,64,255,64,0,0,255,
64,255,80,0,0,255,64,223,128,0,4,255,32,127,246,0,61,251,0,11,255,238,255,210,
0,0,108,238,216,16,0,0,0,0,191,96,0,0,0,0,2,251,0,0,0,0,0,7,
210,0,0,0,0,0,0,0,0,0,0,143,243,0,0,13,252,0,29,251,0,0,127,243,
0,5,255,80,1,239,144,0,0,191,208,9,254,16,0,0,47,247,47,246,0,0,0,7,
254,175,192,0,0,0,0,223,255,48,0,0,0,0,79,249,0,0,0,0,0,15,244,0,
0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,0,0,
0,15,244,0,0,0,255,64,0,0,0,255,64,0,0,0,255,254,220,146,0,255,255,255,
255,64,255,64,2,191,224,255,64,0,47,243,255,64,0,31,244,255,64,1,159,225,255,253,
239,255,96,255,255,254,164,0,255,64,0,0,0,255,64,0,0,0,255,64,0,0,0,4,
190,236,112,0,95,255,239,251,0,207,193,7,255,48,255,80,1,255,64,255,64,7,253,0,
255,68,239,194,0,255,69,255,253,48,255,64,2,191,208,255,64,0,47,243,255,64,0,31,
244,255,64,0,159,241,255,68,223,255,128,255,69,255,215,0,0,223,48,0,0,0,79,144,
0,0,0,8,192,0,0,0,0,0,0,0,2,174,253,113,0,46,255,239,251,0,126,210,
3,255,48,0,0,0,223,64,2,138,206,255,64,95,251,133,255,64,239,112,1,255,64,239,
128,25,255,64,175,254,252,239,64,25,238,162,239,64,0,0,143,128,0,0,0,237,16,0,
0,4,212,0,0,0,0,0,0,0,2,174,253,113,0,46,255,239,251,0,126,210,3,255,
48,0,0,0,223,64,2,138,206,255,64,95,251,133,255,64,239,112,1,255,64,239,128,25,
255,64,175,254,252,239,64,25,238,162,239,64,0,45,250,0,0,1,221,111,144,0,5,131,
5,130,0,0,0,0,0,0,2,174,253,113,0,46,255,239,251,0,126,210,3,255,48,0,
0,0,223,64,2,138,206,255,64,95,251,133,255,64,239,112,1,255,64,239,128,25,255,64,
175,254,252,239,64,25,238,162,239,64,0,18,0,32,0,1,239,168,241,0,5,246,223,160,
0,1,32,2,0,0,2,174,253,113,0,46,255,239,251,0,126,210,3,255,48,0,0,0,
223,64,2,138,206,255,64,95,251,133,255,64,239,112,1,255,64,239,128,25,255,64,175,254,
252,239,64,25,238,162,239,64,4,96,4,96,0,14,243,14,243,0,3,64,3,80,0,0,
0,0,0,0,2,174,253,113,0,46,255,239,251,0,142,194,3,255,48,0,0,1,239,64,
3,139,222,255,64,111,251,133,255,64,239,112,1,255,64,239,128,25,255,64,175,254,252,239,
64,25,238,162,239,64,0,77,252,32,0,1,245,23,208,0,1,244,7,208,0,0,77,252,
32,0,0,0,0,0,0,2,174,253,113,0,46,255,239,251,0,142,194,3,255,48,0,0,
1,239,64,3,139,222,255,64,111,251,133,255,64,239,112,1,255,64,239,128,25,255,64,175,
254,253,239,64,25,238,178,223,64,2,158,253,112,109,253,129,0,46,255,239,255,255,239,252,
16,125,194,2,255,227,2,207,112,0,0,0,223,112,0,79,192,5,190,255,255,255,255,255,
224,143,255,237,255,253,221,221,208,239,145,0,223,112,0,0,0,239,128,6,255,227,0,158,
160,159,254,255,217,255,239,255,64,24,223,217,16,109,254,179,0,0,125,237,163,0,11,255,
239,254,48,127,228,1,191,192,207,128,0,39,64,239,80,0,0,0,239,80,0,0,0,207,
128,0,39,80,127,228,1,175,208,11,255,239,254,48,0,125,253,163,0,0,1,247,32,0,
0,1,157,224,0,0,7,239,160,0,0,1,33,0,0,0,191,80,0,0,0,47,176,0,
0,0,7,210,0,0,0,0,0,0,0,0,141,253,129,0,12,255,239,252,0,127,227,2,
223,112,207,112,0,127,192,239,255,255,255,224,239,253,221,221,208,207,112,0,0,0,127,227,
1,173,128,12,255,239,255,48,1,141,254,163,0,0,0,95,160,0,0,0,206,32,0,0,
2,214,0,0,0,0,0,0,0,0,141,253,129,0,12,255,239,252,0,127,227,2,223,112,
207,112,0,127,192,239,255,255,255,224,239,253,221,221,208,207,112,0,0,0,127,227,1,173,
128,12,255,239,255,48,1,141,254,163,0,0,28,252,16,0,1,206,94,176,0,4,132,4,
131,0,0,0,0,0,0,0,141,253,129,0,12,255,239,252,0,127,227,2,223,112,207,112,
0,127,192,239,255,255,255,224,239,253,221,221,208,207,112,0,0,0,127,227,1,173,128,12,
255,239,255,48,1,141,254,163,0,0,100,4,96,0,3,254,14,243,0,0,83,3,80,0,
0,0,0,0,0,0,141,253,129,0,12,255,239,252,0,127,227,2,223,112,207,112,0,127,
192,239,255,255,255,224,239,253,221,221,208,207,112,0,0,0,127,227,1,173,128,12,255,239,
254,48,1,141,254,163,0,61,192,0,9,245,0,1,236,0,0,0,0,0,255,64,0,255,
64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,64,0,255,
64,8,215,31,209,143,64,0,0,255,64,255,64,255,64,255,64,255,64,255,64,255,64,255,
64,255,64,255,64,0,63,247,0,1,221,191,64,11,244,29,226,0,0,0,0,0,15,244,
0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,0,0,15,244,
0,0,15,244,0,0,15,244,0,0,15,244,0,70,0,70,0,239,48,239,48,52,0,52,
0,0,0,0,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,
0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,255,64,0,0,114,0,
2,0,1,255,150,217,0,0,47,255,128,0,2,206,255,209,0,1,113,10,251,0,1,140,
198,223,80,12,255,255,207,176,127,245,19,223,241,207,144,0,95,243,239,80,0,47,244,239,
96,0,15,243,207,128,0,79,241,127,228,1,207,160,12,255,239,254,32,0,141,253,146,0,
0,17,1,32,0,3,239,138,192,0,8,215,239,96,0,1,16,18,0,0,255,106,238,161,
0,255,255,239,251,0,255,209,8,255,32,255,112,1,255,64,255,80,0,255,64,255,64,0,
255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,0,175,
112,0,0,0,30,208,0,0,0,5,212,0,0,0,0,0,0,0,0,125,253,146,0,12,
255,239,254,32,127,228,2,207,176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,
207,128,0,79,242,127,228,2,207,176,12,255,239,254,32,0,125,254,146,0,0,0,63,209,
0,0,0,175,64,0,0,1,201,0,0,0,0,0,0,0,0,125,253,146,0,12,255,239,
254,32,127,228,2,207,176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,207,128,
0,79,242,127,228,2,207,176,12,255,239,254,32,0,125,254,146,0,0,11,253,32,0,0,
175,93,209,0,3,133,3,133,0,0,0,0,0,0,0,125,253,146,0,12,255,239,254,32,
127,228,2,207,176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,207,128,0,79,
242,127,228,2,207,176,12,255,239,254,32,0,125,254,146,0,0,2,0,33,0,0,191,197,
230,0,2,247,191,225,0,0,32,2,16,0,0,125,253,146,0,12,255,239,254,32,127,228,
2,207,176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,207,128,0,79,242,127,
228,2,207,176,12,255,239,254,32,0,125,254,146,0,0,100,0,100,0,3,254,3,254,0,
0,83,0,83,0,0,0,0,0,0,0,125,253,146,0,12,255,239,254,32,127,228,2,207,
176,207,128,0,79,242,239,96,0,31,244,239,96,0,31,244,207,128,0,79,242,127,228,2,
207,176,12,255,239,254,32,0,125,254,146,0,0,10,209,0,0,0,14,244,0,0,0,8,
176,0,0,0,0,0,0,0,221,221,221,221,208,255,255,255,255,240,0,0,0,0,0,0,
7,176,0,0,0,14,244,0,0,0,10,209,0,0,0,0,0,0,1,0,0,7,223,217,
110,32,0,207,254,255,250,0,7,254,64,63,251,0,12,248,1,207,255,32,14,246,10,210,
255,64,14,246,158,33,255,64,12,255,243,4,255,32,7,255,144,44,251,0,4,255,254,255,
226,0,13,151,223,233,32,0,2,0,0,0,0,0,1,238,16,0,0,0,111,96,0,0,
0,11,160,0,0,0,0,0,0,0,255,64,0,255,64,255,64,0,255,64,255,64,0,255,
64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,96,2,255,64,223,193,9,
255,64,127,255,236,255,64,7,238,162,255,64,0,0,175,80,0,0,2,251,0,0,0,6,
194,0,0,0,0,0,0,0,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,
64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,96,2,255,64,223,193,9,255,64,
127,255,236,255,64,7,238,162,255,64,0,62,247,0,0,2,235,143,80,0,6,129,7,113,
0,0,0,0,0,0,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,
255,64,255,64,0,255,64,255,64,0,255,64,255,96,2,255,64,223,194,26,255,64,127,255,
252,255,64,7,238,162,255,64,4,96,4,96,0,14,243,14,243,0,3,64,3,64,0,0,
0,0,0,0,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,255,64,0,255,64,
255,64,0,255,64,255,64,0,255,64,255,96,2,255,64,223,193,9,255,64,127,255,236,255,
64,7,222,178,255,64,0,0,8,215,0,0,0,0,30,209,0,0,0,0,127,64,0,0,
0,0,0,0,0,0,143,208,0,7,254,0,47,243,0,12,248,0,12,248,0,47,243,0,
6,253,0,127,192,0,1,255,48,207,112,0,0,191,130,255,16,0,0,95,198,251,0,0,
0,14,252,245,0,0,0,9,255,225,0,0,0,4,255,144,0,0,0,2,255,64,0,0,
0,8,253,0,0,0,9,255,245,0,0,0,11,253,112,0,0,0,255,64,0,0,0,255,
64,0,0,0,255,64,0,0,0,255,87,238,179,0,255,191,239,255,64,255,228,2,207,192,
255,128,0,79,242,255,80,0,31,244,255,80,0,31,244,255,128,0,79,242,255,228,2,207,
192,255,207,239,255,48,255,88,238,179,0,255,64,0,0,0,255,64,0,0,0,255,64,0,
0,0,255,64,0,0,0,0,70,0,70,0,0,239,48,239,48,0,52,0,52,0,0,0,
0,0,0,159,192,0,7,253,63,242,0,12,247,13,247,0,63,242,8,252,0,143,192,3,
255,32,223,96,0,207,115,255,16,0,127,200,250,0,0,47,253,245,0,0,12,255,224,0,
0,6,255,144,0,0,4,255,48,0,0,10,252,0,0,10,255,244,0,0,11,253,96,0,
0,0,23,206,255,255,255,255,255,128,2,223,255,223,255,221,221,221,112,13,253,64,4,255,
0,0,0,0,111,226,0,4,255,0,0,0,0,191,144,0,4,255,0,0,0,0,239,96,
0,4,255,221,221,221,0,255,80,0,4,255,255,255,255,0,239,96,0,4,255,0,0,0,
0,191,144,0,4,255,0,0,0,0,111,226,0,4,255,0,0,0,0,12,253,64,4,255,
0,0,0,0,2,223,254,223,255,221,221,221,112,0,23,206,255,255,255,255,255,128,0,125,
253,146,7,223,234,32,0,12,255,239,254,223,254,255,226,0,127,228,2,207,254,64,27,251,
0,207,128,0,79,248,0,3,255,16,239,96,0,31,255,255,255,255,64,239,96,0,31,255,
221,221,221,64,207,128,0,79,249,0,0,0,0,127,228,2,207,254,64,7,219,0,12,255,
239,254,239,254,239,247,0,0,125,254,146,7,206,235,80,0,0,79,144,175,80,0,0,9,
247,250,0,0,0,1,239,225,0,0,0,0,55,48,0,0,0,0,0,0,0,0,0,41,
223,236,96,0,3,239,254,255,250,0,11,252,32,8,255,48,14,245,0,0,0,0,13,251,
16,0,0,0,5,255,234,97,0,0,0,76,255,255,145,0,0,0,37,175,251,0,36,64,
0,4,255,48,111,242,0,1,255,64,47,252,65,59,255,16,7,255,255,255,247,0,0,75,
239,234,64,0,1,206,52,250,0,0,29,222,176,0,0,2,135,16,0,0,0,0,0,0,
0,124,254,180,0,8,255,238,255,48,14,248,0,159,128,13,248,0,0,0,5,255,235,114,
0,0,39,190,255,96,1,32,0,143,224,47,245,0,159,224,9,255,238,255,112,0,124,254,
197,0,0,10,193,10,193,0,0,0,11,226,11,226,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,143,243,0,0,13,252,0,29,252,0,0,127,243,0,5,255,96,1,
239,144,0,0,191,225,9,254,16,0,0,46,248,63,246,0,0,0,7,255,207,192,0,0,
0,0,223,255,48,0,0,0,0,79,249,0,0,0,0,0,15,244,0,0,0,0,0,15,
244,0,0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,0,0,0,15,244,0,0,
0,1,119,0,120,16,0,143,119,249,0,0,11,255,176,0,0,1,119,16,0,0,0,0,
0,0,255,255,255,255,255,221,221,221,223,254,0,0,0,95,244,0,0,2,239,128,0,0,
12,252,0,0,0,159,226,0,0,5,255,80,0,0,46,249,0,0,0,207,192,0,0,8,
254,32,0,0,79,245,0,0,0,239,253,221,221,221,255,255,255,255,255,29,226,46,226,3,
236,207,48,0,95,246,0,0,0,0,0,255,255,255,255,221,221,223,254,0,0,95,244,0,
2,239,128,0,29,251,0,0,175,225,0,7,255,64,0,79,247,0,0,239,254,238,238,255,
255,255,255,221,221,221,221,208,255,255,255,255,240,221,221,221,221,221,221,221,221,221,255,255,
255,255,255,255,255,255,255,6,244,11,242,14,224,79,192,159,160,31,243,63,208,95,144,143,
80,175,16,6,244,10,240,11,242,14,208,14,224,79,160,79,192,143,128,159,160,223,96,31,
243,111,192,63,208,159,128,95,144,191,48,143,80,222,0,175,17,249,0,8,238,128,159,255,
248,239,255,254,239,255,254,159,255,248,9,238,128,139,0,1,183,0,3,196,239,64,4,254,
0,8,250,173,16,1,218,0,4,229,0,4,255,64,0,46,246,0,1,223,128,0,28,250,
0,0,28,250,0,0,1,223,128,0,0,46,246,0,0,4,255,64,29,248,0,0,3,239,
96,0,0,79,244,0,0,6,254,48,0,6,254,48,0,79,244,0,3,239,96,0,29,248,
0,0,
};
static const VGlyph F_TITLE_glyphs[] = {
{32,0,0,0,0,0,64},
{33,0,3,13,1,-13,80},
{34,26,5,5,2,-13,144},
{35,41,12,13,0,-13,176},
{36,119,11,18,0,-16,192},
{37,227,15,13,2,-13,288},
{38,331,11,13,1,-13,192},
{39,409,3,5,2,-13,96},
{40,419,5,17,2,-14,96},
{41,470,6,17,0,-14,96},
{42,521,8,7,0,-13,160},
{43,549,10,9,1,-10,192},
{44,594,4,5,0,-2,80},
{45,604,6,2,1,-7,128},
{46,610,3,3,1,-3,80},
{47,616,7,16,0,-14,112},
{48,680,10,13,1,-13,192},
{49,745,6,13,1,-13,112},
{50,784,9,13,1,-13,176},
{51,849,10,13,1,-13,176},
{52,914,11,13,0,-13,192},
{53,992,10,13,1,-13,176},
{54,1057,10,13,1,-13,176},
{55,1122,9,13,1,-13,160},
{56,1187,10,13,1,-13,176},
{57,1252,10,13,1,-13,176},
{58,1317,3,10,1,-10,80},
{59,1337,4,13,0,-10,96},
{60,1363,9,11,1,-11,192},
{61,1418,8,6,2,-8,192},
{62,1442,9,11,2,-11,192},
{63,1497,9,13,1,-13,160},
{64,1562,17,17,1,-13,288},
{65,1715,13,13,0,-13,208},
{66,1806,10,13,1,-13,192},
{67,1871,12,13,1,-13,208},
{68,1949,11,13,1,-13,208},
{69,2027,9,13,1,-13,176},
{70,2092,9,13,1,-13,176},
{71,2157,12,13,1,-13,208},
{72,2235,11,13,1,-13,208},
{73,2313,3,13,1,-13,80},
{74,2339,9,13,1,-13,160},
{75,2404,11,13,1,-13,208},
{76,2482,8,13,1,-13,160},
{77,2534,14,13,1,-13,256},
{78,2625,11,13,1,-13,224},
{79,2703,12,13,1,-13,224},
{80,2781,10,13,1,-13,192},
{81,2846,12,14,1,-13,224},
{82,2930,10,13,1,-13,192},
{83,2995,11,13,0,-13,192},
{84,3073,10,13,1,-13,192},
{85,3138,11,13,1,-13,208},
{86,3216,13,13,0,-13,208},
{87,3307,18,13,0,-13,288},
{88,3424,13,13,0,-13,208},
{89,3515,13,13,0,-13,208},
{90,3606,10,13,1,-13,192},
{91,3671,4,17,2,-14,96},
{92,3705,7,16,0,-14,112},
{93,3769,5,17,1,-14,96},
{94,3820,8,7,0,-14,144},
{95,3848,8,2,0,0,128},
{96,3856,4,3,1,-14,96},
{97,3862,9,10,1,-10,160},
{98,3912,10,13,1,-13,176},
{99,3977,9,10,1,-10,160},
{100,4027,10,13,1,-13,176},
{101,4092,9,10,1,-10,160},
{102,4142,7,14,1,-14,112},
{103,4198,10,14,1,-10,176},
{104,4268,9,13,1,-13,176},
{105,4333,3,14,1,-14,80},
{106,4361,5,18,-1,-14,80},
{107,4415,9,13,1,-13,160},
{108,4480,3,13,1,-13,80},
{109,4506,14,10,1,-10,256},
{110,4576,9,10,1,-10,176},
{111,4626,10,10,1,-10,176},
{112,4676,10,14,1,-10,176},
{113,4746,10,14,1,-10,176},
{114,4816,6,11,1,-11,112},
{115,4849,9,10,0,-10,160},
{116,4899,7,13,0,-13,96},
{117,4951,9,10,1,-10,176},
{118,5001,11,10,0,-10,160},
{119,5061,15,10,0,-10,240},
{120,5141,10,10,0,-10,160},
{121,5191,11,14,0,-10,160},
{122,5275,8,10,1,-10,160},
{123,5315,6,17,1,-14,128},
{124,5366,3,22,2,-18,96},
{125,5410,7,17,1,-14,128},
{126,5478,10,4,1,-7,192},
{160,5498,0,0,0,0,64},
{161,5498,3,14,1,-10,80},
{162,5526,9,13,1,-13,160},
{163,5591,11,13,0,-13,176},
{164,5669,13,12,0,-12,208},
{165,5753,10,13,0,-13,160},
{166,5818,3,16,2,-13,96},
{167,5850,9,16,0,-13,160},
{168,5930,7,2,2,-13,176},
{169,5938,14,14,1,-14,256},
{170,6036,7,7,1,-13,128},
{171,6064,11,8,0,-10,176},
{172,6112,9,5,2,-7,192},
{173,6137,0,0,0,0,0},
{174,6137,11,11,0,-14,192},
{175,6203,5,2,1,-14,128},
{176,6209,6,6,1,-13,128},
{177,6227,9,10,2,-10,192},
{178,6277,6,7,1,-15,128},
{179,6298,7,7,0,-15,128},
{180,6326,4,3,1,-14,96},
{181,6332,10,14,1,-10,176},
{182,6402,9,13,1,-13,176},
{183,6467,3,3,1,-8,80},
{184,6473,4,5,1,0,96},
{185,6483,4,7,1,-15,96},
{186,6497,8,7,1,-13,144},
{187,6525,11,8,0,-10,176},
{188,6573,14,14,0,-13,240},
{189,6671,14,13,0,-13,240},
{190,6762,15,15,1,-14,256},
{191,6882,9,14,1,-10,160},
{192,6952,13,17,0,-17,208},
{193,7071,13,17,0,-17,208},
{194,7190,13,18,0,-18,208},
{195,7316,13,18,0,-18,208},
{196,7442,13,17,0,-17,208},
{197,7561,13,18,0,-18,208},
{198,7687,18,13,0,-13,288},
{199,7804,12,17,1,-13,208},
{200,7906,9,17,1,-17,176},
{201,7991,9,17,1,-17,176},
{202,8076,9,18,1,-18,176},
{203,8166,9,17,1,-17,176},
{204,8251,5,17,-1,-17,80},
{205,8302,4,17,1,-17,80},
{206,8336,8,18,-2,-18,80},
{207,8408,7,17,-1,-17,80},
{208,8476,13,13,0,-13,208},
{209,8567,11,18,1,-18,224},
{210,8675,12,18,1,-18,224},
{211,8783,12,18,1,-18,224},
{212,8891,12,18,1,-18,224},
{213,8999,12,18,1,-18,224},
{214,9107,12,17,1,-17,224},
{215,9209,10,10,1,-10,192},
{216,9259,12,15,1,-14,224},
{217,9349,11,18,1,-18,208},
{218,9457,11,18,1,-18,208},
{219,9565,11,18,1,-18,208},
{220,9673,11,17,1,-17,208},
{221,9775,13,17,0,-17,208},
{222,9894,10,13,1,-13,192},
{223,9959,10,13,1,-13,176},
{224,10024,9,14,1,-14,160},
{225,10094,9,14,1,-14,160},
{226,10164,9,14,1,-14,160},
{227,10234,9,14,1,-14,160},
{228,10304,9,14,1,-14,160},
{229,10374,9,15,1,-15,160},
{230,10449,15,10,1,-10,256},
{231,10529,10,14,1,-10,160},
{232,10599,9,14,1,-14,160},
{233,10669,9,14,1,-14,160},
{234,10739,9,14,1,-14,160},
{235,10809,9,14,1,-14,160},
{236,10879,5,14,-1,-14,80},
{237,10921,4,14,1,-14,80},
{238,10949,8,14,-2,-14,80},
{239,11005,7,14,-1,-14,80},
{240,11061,10,15,1,-15,176},
{241,11136,9,14,1,-14,176},
{242,11206,10,14,1,-14,176},
{243,11276,10,14,1,-14,176},
{244,11346,10,14,1,-14,176},
{245,11416,10,14,1,-14,176},
{246,11486,10,14,1,-14,176},
{247,11556,9,10,2,-10,192},
{248,11606,11,12,0,-11,176},
{249,11678,9,14,1,-14,176},
{250,11748,9,14,1,-14,176},
{251,11818,9,14,1,-14,176},
{252,11888,9,14,1,-14,176},
{253,11958,11,18,0,-14,160},
{254,12066,10,17,1,-13,176},
{255,12151,10,18,0,-14,160},
{338,12241,17,13,1,-13,288},
{339,12358,17,10,1,-10,288},
{352,12448,11,18,0,-18,192},
{353,12556,9,14,0,-14,160},
{376,12626,13,17,0,-17,208},
{381,12745,10,18,1,-18,192},
{382,12835,8,14,1,-14,160},
{8211,12891,9,2,0,-7,144},
{8212,12901,18,2,0,-7,288},
{8216,12919,4,5,1,-13,80},
{8217,12929,4,5,1,-13,80},
{8220,12939,8,5,1,-13,144},
{8221,12959,7,5,1,-13,144},
{8226,12979,6,6,2,-8,144},
{8230,12997,14,3,1,-3,256},
{8249,13018,7,8,0,-10,112},
{8250,13050,7,8,0,-10,112},
};
static const VFont F_TITLE = { F_TITLE_glyphs, 208, F_TITLE_bmp, 18, 5 };
static const uint8_t F_BODY_bmp[] = {
63,0,63,0,63,0,63,0,63,0,63,0,63,0,0,0,0,0,27,0,29,0,63,79,
0,63,79,0,63,79,0,63,79,0,0,46,0,136,0,0,76,0,165,0,0,105,0,211,
0,31,255,255,255,128,2,215,36,242,16,0,210,4,176,0,0,240,7,144,0,175,255,255,
254,0,39,210,46,114,0,7,128,14,32,0,9,96,31,0,0,0,2,128,0,0,0,3,
240,0,0,1,159,253,96,0,11,200,246,231,0,47,35,240,106,0,47,67,240,0,0,10,
252,241,0,0,0,111,254,112,0,0,3,248,233,0,0,3,240,95,0,109,3,240,94,0,
30,183,247,232,0,2,175,253,112,0,0,3,240,0,0,0,2,128,0,0,6,237,64,0,
37,0,31,104,208,0,128,0,63,20,240,6,64,0,31,54,208,25,0,0,8,255,80,146,
0,0,0,17,5,112,0,0,0,0,27,0,142,80,0,0,148,1,246,208,0,4,160,3,
244,240,0,12,32,1,248,224,0,136,0,0,158,96,0,77,252,48,0,1,232,57,208,0,
3,241,4,240,0,0,229,28,144,0,0,127,250,16,0,0,159,240,0,0,9,212,230,15,
0,47,48,94,109,0,47,32,9,248,0,12,180,59,249,0,2,190,233,62,64,63,0,63,
0,63,0,63,0,0,184,2,242,7,192,11,128,15,64,31,32,63,16,63,16,31,48,13,
112,8,176,3,242,0,184,184,0,94,16,14,80,11,144,7,192,5,224,4,240,4,240,6,
208,9,160,13,96,78,16,184,0,0,63,0,0,73,79,42,32,24,239,214,0,23,239,214,
0,73,79,42,32,0,63,0,0,0,61,0,0,0,63,0,0,0,63,0,0,239,255,255,
176,34,111,50,32,0,63,0,0,0,63,0,0,0,2,0,0,9,176,11,112,14,48,30,
0,255,255,34,34,27,29,0,4,80,0,10,112,0,14,48,0,62,0,0,122,0,0,182,
0,0,226,0,4,208,0,8,144,0,12,96,0,31,32,0,77,0,0,137,0,0,0,93,
236,48,0,4,247,57,226,0,11,144,0,200,0,31,64,0,124,0,47,16,0,78,0,63,
16,0,63,0,47,16,0,78,0,31,64,0,108,0,11,144,0,200,0,4,247,56,226,0,
0,93,236,48,0,4,239,0,111,175,0,164,63,0,16,63,0,0,63,0,0,63,0,0,
63,0,0,63,0,0,63,0,0,63,0,0,63,0,1,174,232,0,11,195,77,144,47,48,
5,224,19,0,5,224,0,0,11,176,0,0,111,32,0,4,246,0,0,46,144,0,1,219,
0,0,11,211,34,32,63,255,255,240,1,174,233,16,12,196,77,144,46,16,5,224,0,0,
5,224,0,0,44,144,0,15,251,0,0,2,94,128,0,0,5,224,74,0,5,224,46,147,
93,144,3,207,232,0,0,0,15,240,0,0,0,108,240,0,0,1,212,240,0,0,9,131,
240,0,0,46,19,240,0,0,184,3,240,0,4,225,3,240,0,13,128,3,240,0,63,255,
255,255,96,2,34,38,243,16,0,0,3,240,0,13,255,255,128,14,98,34,16,31,48,0,
0,63,16,0,0,79,158,214,0,58,99,111,96,0,0,7,208,0,0,4,240,106,0,7,
208,63,131,94,112,4,207,215,0,0,60,253,96,0,2,232,54,230,0,10,144,0,124,0,
14,64,0,0,0,47,58,237,96,0,63,198,55,246,0,63,80,0,125,0,31,16,0,79,
0,12,64,0,125,0,5,229,55,246,0,0,92,237,96,0,255,255,255,240,34,34,43,208,
0,0,47,80,0,0,157,0,0,2,246,0,0,9,208,0,0,31,96,0,0,141,0,0,
1,230,0,0,8,208,0,0,30,96,0,0,1,174,233,16,0,12,180,77,144,0,47,32,
5,224,0,47,32,5,224,0,12,161,44,144,0,2,223,252,32,0,11,213,54,228,0,47,
48,0,93,0,47,32,0,95,0,12,197,53,217,0,1,158,253,112,0,0,125,235,64,0,
8,229,55,227,0,31,80,0,138,0,63,16,0,77,0,31,48,0,127,0,10,210,5,239,
0,1,175,252,110,0,0,1,16,108,0,29,64,0,199,0,10,212,58,209,0,1,158,235,
32,0,27,29,0,0,0,0,27,29,29,27,0,0,0,0,44,73,102,131,0,0,6,96,
0,6,222,64,5,221,97,0,206,96,0,0,158,162,0,0,2,159,163,0,0,2,175,96,
0,0,3,64,255,255,255,34,34,34,0,0,0,255,255,255,34,34,34,163,0,0,0,175,
162,0,0,3,175,146,0,0,2,175,80,0,6,221,48,6,221,80,0,237,96,0,0,96,
0,0,0,7,238,161,0,110,84,202,0,134,0,95,0,0,0,110,0,0,6,247,0,0,
158,96,0,2,244,0,0,2,161,0,0,0,0,0,0,1,176,0,0,1,208,0,0,0,
1,124,238,200,32,0,0,78,182,51,91,228,0,2,232,0,0,0,126,32,10,176,44,217,
240,11,144,14,80,202,59,240,6,208,47,34,242,4,240,4,240,63,19,241,3,240,4,240,
47,18,243,5,241,6,208,15,64,202,58,246,60,128,11,160,61,228,142,234,16,3,246,0,
0,0,0,0,0,111,165,51,85,0,0,0,3,157,254,198,0,0,0,6,246,0,0,0,
11,203,0,0,0,31,79,16,0,0,109,13,80,0,0,184,9,160,0,1,243,4,241,0,
6,224,0,229,0,11,255,255,250,0,31,115,51,127,16,110,0,0,14,80,170,0,0,10,
160,63,255,234,16,0,63,34,60,160,0,63,0,5,240,0,63,0,5,240,0,63,0,44,
144,0,63,255,252,48,0,63,50,37,230,0,63,0,0,110,0,63,0,0,79,0,63,34,
36,218,0,63,255,254,145,0,0,25,222,197,0,1,220,67,126,112,9,192,0,5,241,14,
80,0,0,114,47,32,0,0,0,63,16,0,0,0,47,32,0,0,0,14,80,0,0,114,
9,192,0,5,241,1,220,67,110,112,0,25,222,197,0,63,255,236,112,0,63,50,54,219,
0,63,0,0,46,96,63,0,0,8,176,63,0,0,5,224,63,0,0,4,240,63,0,0,
5,224,63,0,0,8,176,63,0,0,46,96,63,50,54,234,0,63,255,236,96,0,63,255,
255,224,63,34,34,32,63,0,0,0,63,0,0,0,63,0,0,0,63,255,255,144,63,50,
34,16,63,0,0,0,63,0,0,0,63,34,34,32,63,255,255,240,63,255,255,208,63,34,
34,32,63,0,0,0,63,0,0,0,63,0,0,0,63,255,255,96,63,50,34,16,63,0,
0,0,63,0,0,0,63,0,0,0,63,0,0,0,0,42,238,161,0,1,218,52,189,16,
9,176,0,13,128,14,80,0,6,144,47,32,0,0,0,63,16,0,0,0,47,32,10,255,
240,14,80,2,38,240,9,192,0,9,192,1,219,67,143,64,0,42,238,180,0,63,0,0,
3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,255,
255,255,240,63,50,34,37,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,
0,0,3,240,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,
63,0,0,0,3,240,0,0,3,240,0,0,3,240,0,0,3,240,0,0,3,240,0,0,
3,240,0,0,3,240,44,0,3,240,47,32,5,224,12,179,77,144,2,174,233,16,63,32,
0,142,48,63,32,6,245,0,63,32,79,112,0,63,35,232,0,0,63,62,224,0,0,63,
223,225,0,0,63,225,218,0,0,63,48,63,80,0,63,32,8,225,0,63,32,1,217,0,
63,32,0,79,64,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,
0,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,51,51,32,63,255,255,
176,63,208,0,0,30,240,63,243,0,0,95,240,63,185,0,0,186,240,63,110,0,1,246,
240,63,31,80,6,195,240,63,10,160,12,115,240,63,4,241,47,35,240,63,0,230,123,3,
240,63,0,138,198,3,240,63,0,63,241,3,240,63,0,12,160,3,240,63,160,0,3,240,
63,245,0,3,240,63,238,16,3,240,63,111,160,3,240,63,28,245,3,240,63,2,254,19,
240,63,0,127,163,240,63,0,12,247,240,63,0,3,254,240,63,0,0,127,240,63,0,0,
12,240,0,24,223,215,0,0,1,204,83,109,160,0,8,209,0,2,229,0,14,96,0,0,
139,0,47,32,0,0,94,0,63,16,0,0,79,0,47,32,0,0,94,0,14,80,0,0,
139,0,8,209,0,2,229,0,1,204,83,109,160,0,0,24,223,215,0,0,63,255,253,112,
0,63,34,38,231,0,63,0,0,125,0,63,0,0,79,0,63,0,0,110,0,63,0,3,
216,0,63,255,255,144,0,63,50,32,0,0,63,0,0,0,0,63,0,0,0,0,63,0,
0,0,0,0,24,223,215,0,0,1,204,83,109,160,0,8,209,0,2,229,0,14,80,0,
0,139,0,47,32,0,0,94,0,63,16,0,0,79,0,47,32,0,0,94,0,14,96,7,
64,155,0,8,209,5,229,245,0,1,205,100,255,160,0,0,24,223,223,128,0,0,0,0,
3,228,0,63,255,253,112,0,63,34,38,231,0,63,0,0,125,0,63,0,0,79,0,63,
0,0,110,0,63,0,2,217,0,63,255,255,161,0,63,50,79,96,0,63,0,8,225,0,
63,0,1,217,0,63,0,0,95,32,1,158,252,96,0,11,197,54,231,0,47,32,0,106,
0,47,64,0,0,0,10,249,64,0,0,0,109,254,112,0,0,0,39,233,0,0,0,0,
95,0,109,0,0,94,0,30,180,54,232,0,2,174,253,112,0,255,255,255,255,34,95,34,
34,0,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,0,
0,0,63,0,0,0,63,0,0,0,63,0,0,63,0,0,3,240,63,0,0,3,240,63,
0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,
63,16,0,4,240,14,96,0,9,192,6,231,51,143,64,0,92,238,179,0,125,0,0,4,
242,47,48,0,8,192,12,128,0,13,112,7,208,0,63,32,2,243,0,140,0,0,200,0,
215,0,0,109,2,242,0,0,31,55,192,0,0,11,124,112,0,0,6,207,32,0,0,1,
251,0,0,140,0,0,251,0,1,245,79,16,3,255,0,4,241,31,64,7,190,64,7,192,
11,128,10,139,112,11,128,8,176,14,71,176,14,64,4,240,63,20,224,63,16,0,244,108,
0,227,124,0,0,183,168,0,183,168,0,0,138,212,0,122,212,0,0,78,241,0,78,241,
0,0,15,192,0,15,192,0,126,32,0,63,64,12,160,0,201,0,3,245,7,225,0,0,
141,47,80,0,0,13,234,0,0,0,10,246,0,0,0,46,189,16,0,0,187,29,128,0,
6,226,5,243,0,30,112,0,172,0,172,0,0,30,112,172,0,0,30,112,47,96,0,141,
0,8,209,2,245,0,1,231,10,176,0,0,110,79,48,0,0,12,249,0,0,0,4,242,
0,0,0,3,240,0,0,0,3,240,0,0,0,3,240,0,0,0,3,240,0,0,255,255,
255,240,34,34,44,160,0,0,94,32,0,1,230,0,0,9,192,0,0,63,48,0,0,201,
0,0,6,225,0,0,30,80,0,0,172,34,34,32,255,255,255,240,63,248,63,49,63,0,
63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,249,2,33,84,0,
0,107,0,0,46,0,0,14,64,0,10,128,0,6,176,0,2,241,0,0,212,0,0,152,
0,0,92,0,0,31,16,0,12,80,0,8,144,191,240,37,240,3,240,3,240,3,240,3,
240,3,240,3,240,3,240,3,240,3,240,3,240,191,240,34,32,0,143,48,0,239,128,5,
212,224,11,112,213,47,16,123,19,0,20,255,255,255,34,34,34,16,0,154,0,29,32,1,
158,235,32,11,196,59,192,1,16,5,240,0,89,190,240,12,199,85,240,63,32,5,240,31,
131,93,240,4,222,181,240,63,0,0,0,0,63,0,0,0,0,63,0,0,0,0,63,60,
253,80,0,63,197,54,245,0,63,80,0,140,0,63,16,0,79,0,63,16,0,79,0,63,
80,0,140,0,63,197,55,245,0,63,60,253,96,0,0,125,235,48,8,213,57,225,14,80,
0,81,63,32,0,0,63,32,0,0,14,80,0,97,8,213,57,225,0,125,235,48,0,0,
0,63,0,0,0,0,63,0,0,0,0,63,0,0,125,252,95,0,8,229,54,239,0,30,
80,0,143,0,63,32,0,79,0,63,16,0,79,0,14,80,0,143,0,8,229,54,223,0,
0,125,251,63,0,0,109,253,96,0,7,229,53,231,0,14,80,0,109,0,63,255,255,255,
0,63,50,34,34,0,14,80,0,51,0,7,230,53,216,0,0,109,253,112,0,0,109,224,
2,246,32,3,241,0,191,255,192,21,243,32,3,240,0,3,240,0,3,240,0,3,240,0,
3,240,0,3,240,0,0,125,251,79,0,7,229,55,223,0,14,80,0,143,0,63,16,0,
79,0,63,16,0,79,0,30,80,0,143,0,8,229,54,223,0,0,141,251,95,0,0,0,
0,79,0,9,196,37,218,0,1,157,253,145,0,63,0,0,0,63,0,0,0,63,0,0,
0,63,76,251,32,63,180,77,176,63,48,5,240,63,16,3,240,63,0,3,240,63,0,3,
240,63,0,3,240,63,0,3,240,45,0,1,0,0,0,63,0,63,0,63,0,63,0,63,
0,63,0,63,0,63,0,2,208,0,16,0,0,3,240,3,240,3,240,3,240,3,240,3,
240,3,240,3,240,3,240,8,208,62,80,63,64,0,0,63,64,0,0,63,64,0,0,63,
64,11,176,63,64,157,16,63,70,226,0,63,127,64,0,63,255,128,0,63,84,245,0,63,
64,111,48,63,64,9,226,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,
0,63,0,63,0,63,77,210,142,211,0,63,147,173,163,172,0,63,32,79,32,79,0,63,
0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,
0,63,0,63,0,63,76,251,32,63,180,77,176,63,48,5,240,63,16,3,240,63,0,3,
240,63,0,3,240,63,0,3,240,63,0,3,240,0,125,252,80,0,7,229,55,245,0,14,
80,0,140,0,63,32,0,95,0,63,32,0,95,0,14,80,0,140,0,7,229,54,245,0,
0,125,252,80,0,62,61,253,80,0,63,197,54,245,0,63,80,0,140,0,63,16,0,79,
0,63,16,0,79,0,63,80,0,140,0,63,213,55,245,0,63,77,252,80,0,63,0,0,
0,0,63,0,0,0,0,63,0,0,0,0,0,125,251,79,0,8,229,54,223,0,30,80,
0,143,0,63,32,0,79,0,63,16,0,79,0,14,80,0,143,0,8,229,54,223,0,0,
125,251,95,0,0,0,0,63,0,0,0,0,63,0,0,0,0,63,0,63,94,160,63,180,
32,63,32,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,4,222,178,0,31,
115,170,0,63,32,17,0,11,233,64,0,0,73,233,0,19,0,95,0,63,83,173,0,6,
223,179,0,3,240,0,3,240,0,95,255,144,21,243,16,3,240,0,3,240,0,3,240,0,
3,240,0,2,245,32,0,142,160,63,0,3,240,63,0,3,240,63,0,3,240,63,0,3,
240,63,0,3,240,63,32,5,240,14,179,93,240,4,207,181,240,125,0,1,245,31,48,5,
224,11,144,11,144,5,224,31,48,1,228,109,0,0,153,183,0,0,77,226,0,0,15,192,
0,110,0,31,128,7,208,31,48,95,192,11,128,12,112,152,241,14,64,8,176,212,196,78,
0,3,226,224,136,138,0,0,232,176,76,182,0,0,174,96,14,225,0,0,95,32,11,192,
0,46,80,10,176,6,209,78,32,0,184,199,0,0,47,224,0,0,79,224,0,1,214,184,
0,9,192,63,48,79,48,8,208,125,0,1,245,31,64,6,224,11,144,11,144,5,224,31,
48,1,228,109,0,0,153,183,0,0,78,226,0,0,15,192,0,0,13,96,0,3,126,16,
0,13,212,0,0,255,255,255,34,35,233,0,9,209,0,95,48,2,231,0,12,176,0,142,
50,34,255,255,255,0,60,176,0,201,32,0,225,0,0,240,0,0,240,0,4,240,0,143,
160,0,42,192,0,0,240,0,0,240,0,0,225,0,0,198,0,0,78,192,0,0,16,40,
0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,
0,63,0,63,0,63,0,63,0,63,0,172,48,0,43,192,0,4,240,0,3,240,0,3,
241,0,3,244,0,0,223,16,1,234,0,3,241,0,3,240,0,4,240,0,8,208,0,190,
80,0,16,0,0,61,211,2,160,200,110,54,208,210,6,255,80,0,0,17,0,29,0,27,
0,0,0,0,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,0,3,240,0,0,
3,240,0,0,111,253,96,6,233,246,230,14,99,240,68,47,35,240,0,63,19,240,0,30,
83,240,50,8,214,243,216,0,159,255,128,0,4,241,0,0,26,238,112,0,187,53,229,2,
243,0,120,3,241,0,0,3,240,0,0,79,255,255,64,22,243,34,16,3,240,0,0,4,
240,0,19,44,163,51,139,95,255,255,229,0,0,0,0,0,45,90,237,149,209,12,250,52,
191,128,11,160,0,13,128,47,48,0,6,224,63,16,0,4,240,47,48,0,6,224,12,176,
0,14,128,30,249,52,191,160,43,75,238,147,177,30,96,0,156,9,192,1,230,2,243,6,
225,0,184,12,128,0,94,63,32,0,14,250,0,6,255,255,244,0,3,240,0,6,255,255,
244,1,38,243,33,0,3,240,0,63,0,63,0,63,0,63,0,63,0,0,0,0,0,0,
0,63,0,63,0,63,0,63,0,63,0,0,110,229,0,1,246,94,16,2,241,7,16,0,
202,16,0,6,223,211,0,47,50,191,64,47,48,9,208,9,211,4,224,0,143,154,80,0,
3,215,0,2,48,94,0,8,193,126,0,1,223,245,0,0,2,16,0,45,2,208,1,0,
16,0,4,190,234,48,0,0,142,115,73,245,0,5,243,0,0,110,32,12,129,174,213,11,
144,31,43,195,94,69,224,63,63,48,4,52,240,47,79,32,2,21,224,13,125,161,62,74,
160,7,228,223,247,79,64,0,189,82,38,232,0,0,7,239,253,80,0,0,0,2,16,0,
0,4,206,196,7,96,109,1,88,207,29,165,95,47,48,143,8,238,143,0,29,115,228,0,
186,29,112,8,209,186,0,47,85,242,0,8,209,186,0,0,186,29,112,0,29,115,228,207,
255,255,0,34,34,95,0,0,0,63,0,0,0,2,0,0,42,238,145,0,3,233,52,190,
32,13,191,251,27,160,47,95,61,69,224,63,95,255,20,240,14,175,60,57,176,5,248,18,
159,48,0,93,255,195,0,0,0,33,0,0,255,255,34,34,4,220,32,30,121,192,63,20,
240,31,88,208,6,254,64,0,17,0,0,63,0,0,63,0,255,255,255,51,111,51,0,63,
0,0,19,0,255,255,255,34,34,34,110,230,178,94,0,124,6,227,93,48,255,238,59,237,
96,85,6,224,1,238,64,0,7,208,151,6,224,60,236,48,46,48,151,0,63,0,3,240,
63,0,3,240,63,0,3,240,63,0,3,240,63,16,3,240,63,48,6,240,63,195,79,244,
63,142,230,190,63,0,0,0,63,0,0,0,63,0,0,0,8,239,255,0,143,255,95,0,
223,255,63,0,255,255,63,0,223,255,63,0,111,255,63,0,6,205,63,0,0,0,63,0,
0,0,63,0,0,0,63,0,0,0,63,0,27,29,27,32,39,224,93,128,61,240,135,240,
3,240,3,240,3,240,3,240,2,174,233,16,12,144,27,144,47,32,4,224,47,32,4,224,
12,144,27,144,2,190,233,16,46,84,226,0,5,227,141,16,0,141,27,160,0,14,131,244,
0,141,27,160,5,227,141,16,46,84,227,0,78,240,0,3,226,0,150,240,0,12,112,0,
3,240,0,108,0,0,3,240,2,227,0,0,3,240,11,128,17,0,3,240,93,16,255,0,
0,1,228,5,223,0,0,9,160,13,95,0,0,78,16,106,63,0,0,214,0,175,255,176,
8,176,0,34,111,32,1,0,0,0,1,0,60,224,0,3,192,0,168,240,0,12,80,0,
3,240,0,123,0,0,3,240,2,226,0,0,3,240,10,112,0,0,3,240,93,3,205,80,
0,17,212,7,103,224,0,9,160,0,8,192,0,62,16,0,78,32,0,198,0,2,211,0,
7,192,0,11,255,242,2,16,0,2,34,32,5,222,80,0,46,32,12,88,224,0,168,0,
0,6,208,4,209,0,0,207,80,13,64,0,20,56,224,122,0,16,9,221,98,226,15,240,
0,0,11,112,61,240,0,0,92,0,166,240,0,1,212,2,179,240,0,8,144,6,255,251,
0,62,16,1,38,242,0,0,0,0,0,16,0,1,208,0,0,1,176,0,0,0,0,0,
0,3,160,0,0,7,224,0,0,127,96,0,10,228,0,0,47,64,0,0,63,32,8,144,
13,163,94,96,2,191,232,0,0,1,0,0,0,0,13,80,0,0,0,4,192,0,0,0,
0,0,0,0,0,6,246,0,0,0,11,203,0,0,0,31,79,16,0,0,109,13,80,0,
0,184,9,160,0,1,243,4,241,0,6,224,0,229,0,11,255,255,250,0,31,115,51,127,
16,110,0,0,14,80,170,0,0,10,160,0,0,93,0,0,0,0,196,0,0,0,0,0,
0,0,0,6,246,0,0,0,11,203,0,0,0,31,79,16,0,0,109,13,80,0,0,184,
9,160,0,1,243,4,241,0,6,224,0,229,0,11,255,255,250,0,31,115,51,127,16,110,
0,0,14,80,170,0,0,10,160,0,7,230,0,0,0,77,45,48,0,0,0,0,0,0,
0,6,246,0,0,0,11,203,0,0,0,31,79,16,0,0,109,13,80,0,0,184,9,160,
0,1,243,4,241,0,6,224,0,229,0,11,255,255,250,0,31,115,51,127,16,110,0,0,
14,80,170,0,0,10,160,0,45,136,80,0,0,89,157,16,0,0,0,0,0,0,0,6,
246,0,0,0,11,203,0,0,0,31,79,16,0,0,109,13,80,0,0,184,9,160,0,1,
243,4,241,0,6,224,0,229,0,11,255,255,250,0,31,115,51,127,16,110,0,0,14,80,
170,0,0,10,160,0,1,0,16,0,0,29,1,208,0,0,0,0,0,0,0,4,255,0,
0,0,9,239,64,0,0,14,125,128,0,0,79,57,192,0,0,156,5,241,0,0,231,1,
246,0,5,242,0,202,0,10,255,255,254,0,30,147,51,95,48,95,16,0,13,112,170,0,
0,9,176,0,6,237,64,0,0,47,104,224,0,0,47,54,224,0,0,6,237,64,0,0,
0,0,0,0,0,1,252,0,0,0,7,223,16,0,0,12,125,96,0,0,47,41,160,0,
0,140,5,224,0,0,215,1,244,0,4,242,0,184,0,9,255,255,253,0,14,131,51,95,
32,95,16,0,13,112,170,0,0,9,176,0,0,30,239,255,255,224,0,0,109,63,51,51,
48,0,0,215,63,0,0,0,0,5,225,63,0,0,0,0,11,144,63,0,0,0,0,63,
32,63,255,255,144,0,171,0,63,50,34,16,2,255,255,255,0,0,0,8,210,34,111,0,
0,0,30,96,0,63,34,34,32,110,16,0,63,255,255,240,0,25,222,196,0,1,219,67,
127,80,9,192,0,7,224,14,80,0,1,113,47,32,0,0,0,63,16,0,0,0,47,32,
0,0,0,14,80,0,1,113,9,192,0,7,224,1,219,67,127,80,0,25,239,196,0,0,
0,59,32,0,0,0,39,176,0,0,0,107,96,0,0,17,0,0,0,155,0,0,0,29,
32,0,0,0,0,0,63,255,255,224,63,34,34,32,63,0,0,0,63,0,0,0,63,0,
0,0,63,255,255,144,63,50,34,16,63,0,0,0,63,0,0,0,63,34,34,32,63,255,
255,240,0,1,229,0,0,7,160,0,0,0,0,0,63,255,255,224,63,34,34,32,63,0,
0,0,63,0,0,0,63,0,0,0,63,255,255,144,63,50,34,16,63,0,0,0,63,0,
0,0,63,34,34,32,63,255,255,240,0,62,193,0,1,212,138,0,0,0,0,0,63,255,
255,224,63,34,34,32,63,0,0,0,63,0,0,0,63,0,0,0,63,255,255,144,63,50,
34,16,63,0,0,0,63,0,0,0,63,34,34,32,63,255,255,240,0,16,1,0,2,208,
45,0,0,0,0,0,63,255,255,208,63,34,34,32,63,0,0,0,63,0,0,0,63,0,
0,0,63,255,255,112,63,50,34,16,63,0,0,0,63,0,0,0,63,34,34,32,63,255,
255,224,1,0,30,64,6,160,0,0,3,240,3,240,3,240,3,240,3,240,3,240,3,240,
3,240,3,240,3,240,3,240,6,192,13,48,0,0,63,0,63,0,63,0,63,0,63,0,
63,0,63,0,63,0,63,0,63,0,63,0,9,230,0,108,45,48,0,0,0,3,240,0,
3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,
3,240,0,3,240,0,1,0,16,30,146,208,0,0,0,0,63,0,0,63,0,0,63,0,
0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,
3,255,254,199,0,0,3,243,35,109,176,0,3,240,0,2,230,0,3,240,0,0,139,0,
3,240,0,0,94,0,127,255,253,0,79,0,22,243,34,0,94,0,3,240,0,0,139,0,
3,240,0,2,229,0,3,243,35,110,160,0,3,255,254,198,0,0,0,10,197,192,0,0,
29,109,128,0,0,0,0,0,0,63,160,0,3,240,63,245,0,3,240,63,238,16,3,240,
63,111,160,3,240,63,28,245,3,240,63,3,254,19,240,63,0,127,163,240,63,0,12,247,
240,63,0,3,254,240,63,0,0,143,240,63,0,0,12,240,0,0,16,0,0,0,0,2,
228,0,0,0,0,0,107,0,0,0,0,0,0,0,0,0,0,24,223,215,0,0,1,204,
83,109,160,0,8,209,0,2,229,0,14,96,0,0,139,0,47,32,0,0,94,0,63,16,
0,0,79,0,47,32,0,0,94,0,14,80,0,0,139,0,8,209,0,2,229,0,1,204,
83,109,160,0,0,24,223,215,0,0,0,0,2,113,0,0,0,0,8,176,0,0,0,0,
14,48,0,0,0,0,0,0,0,0,0,24,223,215,0,0,1,204,83,109,160,0,8,209,
0,2,229,0,14,96,0,0,139,0,47,32,0,0,94,0,63,16,0,0,79,0,47,32,
0,0,94,0,14,80,0,0,139,0,8,209,0,2,229,0,1,204,83,109,160,0,0,24,
223,215,0,0,0,0,174,96,0,0,0,7,194,212,0,0,0,0,0,0,0,0,0,24,
223,215,0,0,1,204,83,109,160,0,8,209,0,2,229,0,14,96,0,0,139,0,47,32,
0,0,94,0,63,16,0,0,79,0,47,32,0,0,94,0,14,80,0,0,139,0,8,209,
0,2,229,0,1,204,83,109,160,0,0,24,223,215,0,0,0,4,216,134,0,0,0,8,
138,210,0,0,0,0,0,0,0,0,0,24,223,215,0,0,1,204,83,109,160,0,8,209,
0,2,229,0,14,96,0,0,139,0,47,32,0,0,94,0,63,16,0,0,79,0,47,32,
0,0,94,0,14,80,0,0,139,0,8,209,0,2,229,0,1,204,83,109,160,0,0,24,
223,215,0,0,0,1,0,16,0,0,0,29,1,208,0,0,0,0,0,0,0,0,0,24,
223,215,0,0,1,204,83,109,160,0,8,209,0,2,229,0,14,96,0,0,139,0,47,32,
0,0,94,0,63,16,0,0,79,0,47,32,0,0,94,0,14,80,0,0,139,0,8,209,
0,2,229,0,1,204,83,109,160,0,0,24,223,215,0,0,3,0,1,32,94,48,28,144,
7,227,203,0,0,143,225,0,0,191,226,0,10,193,157,32,92,16,9,144,1,0,0,0,
0,0,0,0,16,0,0,24,223,215,196,0,1,204,83,111,240,0,8,209,0,63,245,0,
14,96,1,214,139,0,47,32,8,176,94,0,63,16,78,32,79,0,47,33,214,0,94,0,
14,89,176,0,139,0,8,255,32,2,229,0,2,255,83,109,160,0,7,184,223,215,0,0,
0,16,0,0,0,0,0,1,16,0,0,0,9,176,0,0,0,1,212,0,0,0,0,0,
0,0,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,
0,3,240,63,0,0,3,240,63,0,0,3,240,63,16,0,4,240,14,96,0,9,192,6,
231,51,143,64,0,92,238,179,0,0,0,6,80,0,0,0,31,64,0,0,0,122,0,0,
0,0,0,0,0,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,
240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,16,0,4,240,14,96,0,
9,192,6,231,51,143,64,0,92,238,179,0,0,3,237,16,0,0,46,71,193,0,0,0,
0,0,0,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,
0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,16,0,4,240,14,96,0,9,192,
6,231,51,143,64,0,92,238,179,0,0,1,0,16,0,0,29,1,208,0,0,0,0,0,
0,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,3,240,63,0,0,
3,240,63,0,0,3,240,63,0,0,3,240,63,16,0,4,240,14,96,0,9,192,6,231,
51,143,64,0,92,238,179,0,0,0,108,0,0,0,0,211,0,0,0,0,0,0,0,172,
0,0,30,112,47,96,0,141,0,8,209,2,245,0,1,231,10,176,0,0,110,79,48,0,
0,12,249,0,0,0,4,242,0,0,0,3,240,0,0,0,3,240,0,0,0,3,240,0,
0,0,3,240,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,255,232,0,63,51,
94,144,63,0,5,224,63,0,5,240,63,0,43,160,63,255,251,16,63,50,16,0,63,0,
0,0,2,190,233,16,13,179,77,160,47,32,5,240,63,0,5,224,63,0,28,112,63,9,
252,16,63,1,77,160,63,0,5,224,63,0,4,240,63,1,60,176,63,10,251,32,0,17,
0,0,0,125,0,0,0,12,80,0,0,0,0,0,1,158,235,32,11,196,59,192,1,16,
5,240,0,89,190,240,12,199,85,240,63,32,5,240,31,131,93,240,4,222,181,240,0,0,
200,0,0,4,192,0,0,0,0,0,1,158,235,32,11,196,59,192,1,16,5,240,0,89,
190,240,12,199,85,240,63,32,5,240,31,131,93,240,4,222,181,240,0,60,195,0,0,114,
39,0,0,0,0,0,1,158,235,32,11,196,59,192,1,16,5,240,0,89,190,240,12,199,
85,240,63,32,5,240,31,131,93,240,4,222,181,240,0,156,92,0,0,213,201,0,0,0,
0,0,1,158,235,32,11,196,59,192,1,16,5,240,0,89,190,240,12,199,85,240,63,32,
5,240,31,131,93,240,4,222,181,240,2,208,45,0,0,16,1,0,0,0,0,0,1,158,
235,32,11,196,60,192,1,16,6,240,1,89,190,240,12,199,85,240,47,32,5,240,31,131,
93,240,4,222,181,240,0,0,16,0,0,127,248,0,0,230,143,0,0,109,230,0,0,0,
0,0,1,158,235,32,11,163,77,192,1,0,8,240,1,105,190,240,13,199,85,240,47,32,
5,240,30,131,93,240,4,222,197,240,2,158,236,107,237,129,0,11,179,79,254,67,187,0,
0,0,13,245,0,31,32,3,174,255,255,255,255,64,30,164,47,244,34,34,16,63,16,14,
245,0,20,0,30,131,127,253,67,173,0,4,223,196,26,238,162,0,0,142,235,32,8,212,
58,208,14,80,0,81,63,32,0,0,63,32,0,0,30,80,0,97,8,212,58,208,0,143,
251,32,0,3,195,0,0,1,107,0,0,6,182,0,0,17,0,0,0,0,63,64,0,0,
0,7,176,0,0,0,0,0,0,0,0,109,253,96,0,7,229,53,231,0,14,80,0,109,
0,63,255,255,255,0,63,50,34,34,0,14,80,0,51,0,7,230,53,216,0,0,109,253,
112,0,0,0,110,32,0,0,0,213,0,0,0,0,0,0,0,0,109,253,96,0,7,229,
53,231,0,14,80,0,109,0,63,255,255,255,0,63,50,34,34,0,14,80,0,51,0,7,
230,53,216,0,0,109,253,112,0,0,10,232,0,0,0,155,29,96,0,0,0,0,0,0,
0,109,253,96,0,7,229,53,231,0,14,80,0,109,0,63,255,255,255,0,63,50,34,34,
0,14,80,0,51,0,7,230,53,216,0,0,109,253,112,0,2,208,45,0,0,0,16,1,
0,0,0,0,0,0,0,0,109,253,96,0,7,229,53,231,0,14,80,0,109,0,63,255,
255,255,0,63,50,34,34,0,14,80,0,50,0,7,230,53,215,0,0,109,253,112,0,1,
0,30,64,6,176,0,0,3,240,3,240,3,240,3,240,3,240,3,240,3,240,3,240,6,
209,13,48,0,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,63,0,9,230,0,
108,45,48,0,0,0,3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,3,240,0,
3,240,0,3,240,0,45,0,192,1,0,16,0,0,0,0,63,0,0,63,0,0,63,0,
0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,70,0,32,0,0,60,252,176,
0,0,93,254,16,0,0,50,9,160,0,0,125,216,228,0,7,230,55,234,0,14,96,0,
158,0,63,32,0,95,0,63,32,0,79,0,14,80,0,139,0,7,229,54,244,0,0,125,
252,80,0,0,156,91,0,0,214,215,0,0,0,0,0,63,92,252,32,63,212,76,176,63,
80,5,240,63,32,3,240,63,32,3,240,63,32,3,240,63,32,3,240,63,32,3,240,0,
17,0,0,0,0,47,48,0,0,0,7,176,0,0,0,0,0,0,0,0,125,252,80,0,
7,229,55,245,0,14,80,0,140,0,63,32,0,95,0,63,32,0,95,0,14,80,0,140,
0,7,229,54,245,0,0,125,252,80,0,0,0,109,16,0,0,0,212,0,0,0,0,0,
0,0,0,125,252,80,0,7,229,55,245,0,14,80,0,140,0,63,32,0,95,0,63,32,
0,95,0,14,80,0,140,0,7,229,54,245,0,0,125,252,80,0,0,10,231,0,0,0,
139,45,80,0,0,0,0,0,0,0,125,252,80,0,7,229,55,245,0,14,80,0,140,0,
63,32,0,95,0,63,32,0,95,0,14,80,0,140,0,7,229,54,245,0,0,125,252,80,
0,0,93,136,96,0,0,151,173,32,0,0,0,0,0,0,0,125,252,80,0,7,229,55,
245,0,14,80,0,140,0,63,32,0,95,0,63,32,0,95,0,14,80,0,140,0,7,229,
54,245,0,0,125,252,80,0,2,208,45,0,0,0,16,1,0,0,0,0,0,0,0,0,
125,252,80,0,7,229,55,245,0,14,80,0,140,0,63,32,0,95,0,63,32,0,95,0,
14,80,0,140,0,7,229,54,245,0,0,125,252,80,0,0,29,0,0,27,0,0,0,0,
255,255,255,34,34,34,0,26,0,0,45,0,0,0,0,1,0,0,125,252,141,0,7,229,
56,249,0,14,80,12,252,0,63,32,153,95,0,63,39,176,95,0,14,173,16,140,0,10,
248,54,245,0,12,205,252,80,0,1,0,0,0,0,0,17,0,0,0,155,0,0,0,29,
48,0,0,0,0,0,63,0,3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,0,
3,240,63,32,5,240,14,179,93,240,4,207,181,240,0,0,214,0,0,6,176,0,0,0,
0,0,63,0,3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,32,
5,240,14,179,93,240,4,207,181,240,0,62,209,0,1,212,123,0,0,0,0,0,63,0,
3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,32,5,240,14,179,
93,240,4,207,181,240,2,208,45,0,0,16,1,0,0,0,0,0,63,0,3,240,63,0,
3,240,63,0,3,240,63,0,3,240,63,0,3,240,63,32,5,240,14,179,92,240,4,223,
181,240,0,0,214,0,0,6,176,0,0,0,0,0,125,0,1,245,31,64,6,224,11,144,
11,144,5,224,31,48,1,228,109,0,0,153,183,0,0,78,226,0,0,15,192,0,0,13,
96,0,3,126,16,0,13,212,0,0,63,0,0,0,0,63,0,0,0,0,63,0,0,0,
0,63,60,253,80,0,63,197,54,245,0,63,80,0,140,0,63,16,0,79,0,63,16,0,
79,0,63,80,0,140,0,63,213,55,245,0,63,77,252,80,0,63,0,0,0,0,63,0,
0,0,0,63,0,0,0,0,2,208,45,0,0,16,1,0,0,0,0,0,110,0,2,242,
31,80,8,192,11,160,13,112,6,225,63,32,2,245,139,0,0,201,214,0,0,127,241,0,
0,47,176,0,0,47,80,0,2,158,0,0,12,212,0,0,0,25,223,255,255,255,224,1,
204,66,95,34,34,32,8,192,0,63,0,0,0,14,80,0,63,0,0,0,47,32,0,63,
0,0,0,63,16,0,63,255,255,144,47,32,0,63,50,34,16,14,80,0,63,0,0,0,
8,192,0,63,0,0,0,1,204,66,95,34,34,32,0,25,223,255,255,255,240,0,125,252,
80,109,253,96,0,7,229,55,252,229,54,231,0,14,80,0,159,80,0,109,0,63,32,0,
127,255,255,255,0,63,32,0,127,50,34,34,0,14,80,0,159,80,0,51,0,7,229,54,
252,230,53,217,0,0,125,252,80,109,253,112,0,0,153,62,48,0,0,11,229,0,0,0,
0,0,0,0,1,158,252,96,0,11,197,54,231,0,47,32,0,106,0,47,64,0,0,0,
10,249,64,0,0,0,109,254,112,0,0,0,39,233,0,0,0,0,95,0,109,0,0,94,
0,30,180,54,232,0,2,174,253,112,0,4,195,209,0,0,126,64,0,0,0,0,0,4,
222,178,0,31,115,170,0,63,32,17,0,11,233,64,0,0,73,233,0,19,0,95,0,63,
83,173,0,6,223,179,0,0,1,0,16,0,0,30,145,208,0,0,0,0,0,0,46,112,
0,8,224,6,225,0,30,112,0,186,0,126,16,0,47,65,231,0,0,7,198,225,0,0,
0,223,128,0,0,0,79,16,0,0,0,63,0,0,0,0,63,0,0,0,0,63,0,0,
0,0,63,0,0,4,210,213,0,0,126,112,0,0,0,0,0,255,255,255,240,34,34,44,
160,0,0,94,32,0,1,230,0,0,9,192,0,0,63,48,0,0,201,0,0,6,225,0,
0,30,80,0,0,172,34,34,32,255,255,255,240,28,102,193,2,221,32,0,0,0,255,255,
255,34,35,233,0,9,209,0,95,48,2,231,0,12,176,0,142,50,34,255,255,255,255,255,
255,240,34,34,34,32,255,255,255,255,255,255,255,34,34,34,34,34,34,34,14,16,77,0,
139,0,201,0,110,0,139,0,166,0,210,0,14,23,128,77,11,96,139,14,64,201,79,16,
110,13,112,139,15,48,166,46,0,210,90,0,61,211,207,252,255,255,207,252,61,211,27,0,
27,0,27,29,0,29,0,29,0,29,112,0,186,0,8,209,0,47,80,0,8,209,0,0,
186,0,0,29,112,46,80,0,5,227,0,0,141,16,0,14,128,0,141,16,5,227,0,46,
80,0,
};
static const VGlyph F_BODY_glyphs[] = {
{32,0,0,0,0,0,64},
{33,0,3,11,0,-11,64},
{34,22,5,4,1,-11,112},
{35,34,9,11,0,-11,144},
{36,89,9,15,0,-13,144},
{37,164,12,11,1,-11,224},
{38,230,9,11,0,-11,144},
{39,285,3,4,1,-11,64},
{40,293,4,13,1,-11,80},
{41,319,4,13,1,-11,80},
{42,345,7,6,0,-11,112},
{43,369,7,8,1,-8,144},
{44,401,3,4,0,-1,64},
{45,409,4,2,1,-5,96},
{46,413,2,2,0,-2,64},
{47,415,5,13,0,-11,80},
{48,454,9,11,0,-11,144},
{49,509,5,11,0,-11,96},
{50,542,8,11,0,-11,144},
{51,586,8,11,0,-11,144},
{52,630,9,11,0,-11,144},
{53,685,8,11,0,-11,128},
{54,729,9,11,0,-11,144},
{55,784,7,11,1,-11,128},
{56,828,9,11,0,-11,144},
{57,883,9,11,0,-11,144},
{58,938,2,8,0,-8,64},
{59,946,2,10,0,-7,64},
{60,956,7,8,1,-8,144},
{61,988,6,5,2,-6,144},
{62,1003,7,8,2,-8,144},
{63,1035,7,11,0,-11,112},
{64,1079,14,13,0,-10,224},
{65,1170,9,11,0,-11,160},
{66,1225,9,11,0,-11,144},
{67,1280,10,11,0,-11,160},
{68,1335,10,11,0,-11,160},
{69,1390,7,11,0,-11,128},
{70,1434,7,11,0,-11,128},
{71,1478,10,11,0,-11,160},
{72,1533,10,11,0,-11,160},
{73,1588,3,11,0,-11,64},
{74,1610,8,11,0,-11,128},
{75,1654,9,11,0,-11,144},
{76,1709,7,11,0,-11,128},
{77,1753,12,11,0,-11,208},
{78,1819,10,11,0,-11,176},
{79,1874,11,11,0,-11,176},
{80,1940,9,11,0,-11,144},
{81,1995,11,12,0,-11,176},
{82,2067,9,11,0,-11,144},
{83,2122,9,11,0,-11,144},
{84,2177,8,11,1,-11,144},
{85,2221,10,11,0,-11,160},
{86,2276,10,11,0,-11,160},
{87,2331,14,11,0,-11,224},
{88,2408,9,11,0,-11,160},
{89,2463,9,11,0,-11,160},
{90,2518,7,11,1,-11,144},
{91,2562,4,14,1,-11,80},
{92,2590,5,13,0,-11,80},
{93,2629,4,14,1,-11,80},
{94,2657,6,6,0,-11,112},
{95,2675,6,2,0,0,96},
{96,2681,3,3,1,-12,80},
{97,2687,8,8,0,-8,128},
{98,2719,9,11,0,-11,144},
{99,2774,8,8,0,-8,128},
{100,2806,9,11,0,-11,144},
{101,2861,9,8,0,-8,128},
{102,2901,6,11,0,-11,80},
{103,2934,9,11,0,-8,144},
{104,2989,8,11,0,-11,128},
{105,3033,3,11,0,-11,48},
{106,3055,4,14,-1,-11,48},
{107,3083,8,11,0,-11,128},
{108,3127,3,11,0,-11,48},
{109,3149,11,8,0,-8,192},
{110,3197,8,8,0,-8,128},
{111,3229,9,8,0,-8,128},
{112,3269,9,11,0,-8,144},
{113,3324,9,11,0,-8,144},
{114,3379,5,8,0,-8,80},
{115,3403,7,8,0,-8,112},
{116,3435,5,10,-1,-10,80},
{117,3465,8,8,0,-8,128},
{118,3497,8,8,0,-8,128},
{119,3529,11,8,0,-8,176},
{120,3577,8,8,0,-8,128},
{121,3609,8,11,0,-8,128},
{122,3653,6,8,1,-8,128},
{123,3677,5,14,0,-11,96},
{124,3719,3,18,1,-15,80},
{125,3755,5,14,1,-11,96},
{126,3797,7,4,1,-6,144},
{160,3813,0,0,0,0,64},
{161,3813,3,11,0,-8,64},
{162,3835,8,11,0,-11,128},
{163,3879,8,11,0,-11,144},
{164,3923,10,10,0,-10,160},
{165,3973,8,11,-1,-11,128},
{166,4017,3,13,1,-11,64},
{167,4043,8,14,0,-11,128},
{168,4099,5,2,1,-11,128},
{169,4105,12,12,0,-11,208},
{170,4177,6,6,0,-11,96},
{171,4195,8,7,0,-8,128},
{172,4223,7,4,1,-5,144},
{173,4239,0,0,0,0,0},
{174,4239,10,9,0,-11,144},
{175,4284,4,2,1,-11,112},
{176,4288,6,6,0,-11,96},
{177,4306,6,8,1,-8,144},
{178,4330,4,6,1,-12,96},
{179,4342,5,6,1,-12,96},
{180,4360,3,2,1,-11,80},
{181,4364,8,11,0,-8,128},
{182,4408,7,11,1,-11,128},
{183,4452,2,2,0,-6,64},
{184,4454,3,3,0,0,64},
{185,4460,4,6,0,-12,64},
{186,4472,7,6,0,-11,112},
{187,4496,8,7,0,-8,128},
{188,4524,11,12,0,-11,176},
{189,4596,12,12,0,-11,192},
{190,4668,12,12,0,-11,192},
{191,4740,7,11,0,-8,112},
{192,4784,9,15,0,-15,160},
{193,4859,9,14,0,-14,160},
{194,4929,9,14,0,-14,160},
{195,4999,9,14,0,-14,160},
{196,5069,9,14,0,-14,160},
{197,5139,9,16,0,-16,160},
{198,5219,13,11,0,-11,224},
{199,5296,10,14,0,-11,160},
{200,5366,7,15,0,-15,128},
{201,5426,7,14,0,-14,128},
{202,5482,7,14,0,-14,128},
{203,5538,7,14,0,-14,128},
{204,5594,4,15,-1,-15,64},
{205,5624,4,14,0,-14,64},
{206,5652,5,14,-1,-14,64},
{207,5694,5,14,-1,-14,64},
{208,5736,11,11,0,-11,160},
{209,5802,10,14,0,-14,176},
{210,5872,11,15,0,-15,176},
{211,5962,11,15,0,-15,176},
{212,6052,11,14,0,-14,176},
{213,6136,11,14,0,-14,176},
{214,6220,11,14,0,-14,176},
{215,6304,7,8,1,-8,144},
{216,6336,11,13,0,-12,176},
{217,6414,10,15,0,-15,160},
{218,6489,10,15,0,-15,160},
{219,6564,10,14,0,-14,160},
{220,6634,10,14,0,-14,160},
{221,6704,9,14,0,-14,160},
{222,6774,8,11,0,-11,144},
{223,6818,8,11,0,-11,144},
{224,6862,8,12,0,-12,128},
{225,6910,8,11,0,-11,128},
{226,6954,8,11,0,-11,128},
{227,6998,8,11,0,-11,128},
{228,7042,8,11,0,-11,128},
{229,7086,8,13,0,-13,128},
{230,7138,13,8,0,-8,208},
{231,7194,8,11,0,-8,128},
{232,7238,9,12,0,-12,128},
{233,7298,9,11,0,-11,128},
{234,7353,9,11,0,-11,128},
{235,7408,9,11,0,-11,128},
{236,7463,4,12,-1,-12,48},
{237,7487,4,11,0,-11,48},
{238,7509,5,11,-1,-11,48},
{239,7542,5,11,-1,-11,48},
{240,7575,9,12,0,-12,128},
{241,7635,8,11,0,-11,128},
{242,7679,9,12,0,-12,128},
{243,7739,9,11,0,-11,128},
{244,7794,9,11,0,-11,128},
{245,7849,9,11,0,-11,128},
{246,7904,9,11,0,-11,128},
{247,7959,6,7,1,-8,144},
{248,7980,9,10,0,-9,128},
{249,8030,8,12,0,-12,128},
{250,8078,8,11,0,-11,128},
{251,8122,8,11,0,-11,128},
{252,8166,8,11,0,-11,128},
{253,8210,8,14,0,-11,128},
{254,8266,9,14,0,-11,144},
{255,8336,8,14,0,-11,128},
{338,8392,13,11,0,-11,224},
{339,8469,15,8,0,-8,224},
{352,8533,9,14,0,-14,144},
{353,8603,7,11,0,-11,112},
{376,8647,10,14,0,-14,160},
{381,8717,7,14,1,-14,144},
{382,8773,6,11,1,-11,128},
{8211,8806,7,2,0,-5,112},
{8212,8814,14,2,0,-5,224},
{8216,8828,3,4,1,-11,64},
{8217,8836,3,4,1,-11,64},
{8220,8844,5,4,1,-11,96},
{8221,8856,5,4,1,-11,96},
{8226,8868,4,5,2,-7,128},
{8230,8878,10,2,0,-2,192},
{8249,8888,5,7,0,-8,80},
{8250,8909,5,7,0,-8,80},
};
static const VFont F_BODY = { F_BODY_glyphs, 208, F_BODY_bmp, 14, 4 };
static const uint8_t F_BTN_bmp[] = {
255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,0,0,137,0,255,0,171,0,
255,31,241,255,31,241,255,31,241,255,31,241,255,31,241,0,13,176,11,208,0,0,15,144,
14,160,0,0,47,96,31,128,0,14,255,255,255,255,0,26,255,202,255,217,0,0,158,0,
143,16,0,0,204,0,189,0,0,106,255,170,255,163,0,191,255,255,255,243,0,4,245,3,
246,0,0,6,242,5,244,0,0,9,240,8,241,0,0,0,0,15,0,0,0,0,15,0,
0,0,75,239,197,0,5,255,207,255,96,13,245,15,127,208,14,242,15,21,80,12,252,79,
0,0,4,239,255,162,0,0,39,207,255,64,0,0,15,191,192,73,112,15,47,240,95,243,
15,111,192,12,255,191,255,80,1,141,255,180,0,0,0,15,0,0,0,0,7,0,0,44,
252,48,0,3,128,0,207,223,192,0,10,16,0,255,47,241,0,131,0,0,239,79,240,3,
128,0,0,159,255,160,10,0,0,0,7,167,16,131,0,0,0,0,0,3,128,44,253,48,
0,0,10,0,191,143,192,0,0,131,0,255,31,241,0,3,128,0,255,63,241,0,10,0,
0,191,223,192,0,131,0,0,44,252,48,1,158,234,16,0,10,253,223,176,0,15,242,31,
240,0,15,242,79,208,0,10,255,239,80,0,2,255,244,0,0,28,255,244,15,240,175,166,
254,95,224,255,32,159,255,144,223,80,15,255,48,111,251,207,255,128,5,207,234,94,245,255,
16,255,16,255,16,255,16,255,16,4,251,11,245,31,224,95,160,159,112,207,64,223,48,255,
16,239,32,223,48,191,96,127,160,47,224,11,244,4,250,175,80,0,79,192,0,14,242,0,
10,246,0,6,250,0,3,253,0,2,255,0,1,255,16,1,255,0,2,254,0,5,251,0,
9,248,0,13,243,0,63,192,0,175,80,0,0,63,0,0,71,63,25,16,110,223,222,64,
3,239,209,0,110,223,221,64,71,63,25,16,0,63,0,0,0,15,241,0,0,15,241,0,
0,15,241,0,187,191,252,187,255,255,255,255,0,15,241,0,0,15,241,0,0,15,241,0,
7,248,9,244,11,224,13,176,15,112,170,170,160,255,255,240,137,0,255,0,171,0,0,0,
133,0,4,248,0,8,244,0,12,240,0,31,176,0,79,112,0,143,48,0,206,0,1,251,
0,5,247,0,9,243,0,13,224,0,47,160,0,95,96,0,159,32,0,0,125,253,112,0,
10,254,190,250,0,95,209,1,223,80,175,96,0,127,160,223,48,0,63,208,239,16,0,31,
224,239,16,0,31,224,223,48,0,63,208,175,96,0,127,160,95,209,1,223,80,11,254,190,
251,0,0,141,253,128,0,2,207,241,127,255,241,253,63,241,145,15,241,0,15,241,0,15,
241,0,15,241,0,15,241,0,15,241,0,15,241,0,15,241,0,15,241,4,190,235,64,79,
252,207,245,207,112,7,252,187,32,2,254,0,0,5,252,0,0,29,246,0,1,207,160,0,
28,251,0,1,207,177,0,28,251,16,0,223,251,170,170,255,255,255,255,4,190,236,96,0,
95,253,207,247,0,223,128,5,254,0,68,16,1,255,0,0,0,41,250,0,0,31,255,128,
0,0,10,207,228,0,0,0,6,253,0,85,16,0,255,0,223,96,6,253,0,95,252,207,
246,0,5,190,235,80,0,0,0,13,255,16,0,0,143,255,16,0,2,251,255,16,0,11,
243,255,16,0,95,144,255,16,1,222,16,255,16,8,247,0,255,16,47,208,0,255,16,127,
255,255,255,248,90,170,170,255,181,0,0,0,255,16,0,0,0,255,16,79,255,255,247,0,
111,202,170,165,0,127,96,0,0,0,159,64,0,0,0,175,125,252,64,0,191,252,207,244,
0,54,48,9,252,0,0,0,1,255,0,102,0,1,255,0,223,80,8,251,0,95,252,207,
243,0,5,206,235,48,0,0,108,254,163,0,8,254,189,254,48,63,210,0,159,176,159,96,
0,2,32,223,56,222,179,0,239,206,189,255,48,255,177,0,175,176,239,48,0,47,224,191,
32,0,47,224,111,176,0,191,160,11,253,189,254,32,0,141,254,146,0,255,255,255,255,170,
170,172,255,0,0,9,249,0,0,31,242,0,0,143,144,0,1,239,32,0,8,250,0,0,
30,243,0,0,127,160,0,1,239,48,0,7,251,0,0,30,243,0,0,5,190,236,80,0,
111,252,207,247,0,223,96,5,254,0,239,32,1,255,0,159,162,25,249,0,8,255,255,144,
0,46,253,189,250,16,191,128,0,143,160,255,16,0,47,224,223,112,0,143,208,95,253,189,
255,80,3,174,254,163,0,2,158,253,112,0,46,253,189,251,0,191,160,0,175,96,239,32,
0,47,176,239,48,0,79,224,175,211,20,223,240,45,255,255,143,224,1,122,148,63,192,86,
32,0,127,144,175,144,2,239,48,46,253,190,248,0,2,174,252,96,0,137,0,255,0,171,
0,0,0,0,0,0,0,137,0,255,0,171,0,8,144,15,240,10,176,0,0,0,0,0,
0,0,0,14,176,31,128,47,80,79,16,109,0,0,0,0,1,0,0,2,142,0,2,159,
255,4,175,253,113,207,251,80,0,255,195,0,0,92,255,181,0,0,75,255,215,0,0,58,
255,0,0,0,40,170,170,170,170,255,255,255,255,0,0,0,0,0,0,0,0,170,170,170,
170,255,255,255,255,16,0,0,0,232,32,0,0,255,249,32,0,23,223,250,64,0,5,191,
252,0,0,59,255,0,91,255,197,125,255,180,0,255,163,0,0,130,0,0,0,7,223,216,
0,127,235,239,144,239,32,63,240,51,0,31,240,0,2,207,160,0,78,250,16,0,223,112,
0,0,255,32,0,0,0,0,0,0,137,0,0,0,255,0,0,0,171,0,0,0,1,123,
239,236,130,0,0,0,94,255,203,190,255,112,0,5,255,129,0,0,94,247,0,30,245,23,
168,42,163,239,32,127,177,223,255,239,240,143,144,191,89,251,18,175,240,63,208,239,46,243,
0,63,240,31,240,255,47,241,0,31,240,15,240,239,47,242,0,47,240,31,224,207,91,248,
0,143,241,95,160,143,164,255,204,253,252,239,64,47,245,76,253,83,207,214,0,6,255,129,
0,0,0,0,0,0,111,255,203,188,229,0,0,0,1,123,239,236,147,0,0,0,0,143,
247,0,0,0,0,223,252,0,0,0,3,251,223,32,0,0,8,247,159,112,0,0,13,243,
95,192,0,0,63,224,31,242,0,0,143,160,11,247,0,0,223,251,191,253,0,3,255,255,
255,255,48,8,250,0,0,191,128,13,246,0,0,111,208,63,241,0,0,31,243,255,255,253,
128,0,255,186,191,249,0,255,16,4,255,0,255,16,1,255,0,255,16,25,249,0,255,255,
255,161,0,255,186,189,252,32,255,16,0,143,176,255,16,0,47,224,255,16,0,111,208,255,
186,172,255,96,255,255,254,197,0,0,58,223,216,16,0,5,255,219,239,209,0,46,245,0,
26,251,0,159,144,0,1,239,32,223,64,0,0,0,0,239,32,0,0,0,0,239,32,0,
0,0,0,223,64,0,0,0,0,159,144,0,1,239,32,47,245,0,26,251,0,5,255,219,
239,209,0,0,58,239,216,16,0,255,255,253,146,0,255,186,190,254,80,255,16,0,111,226,
255,16,0,9,248,255,16,0,4,253,255,16,0,2,254,255,16,0,2,254,255,16,0,4,
253,255,16,0,9,248,255,16,0,111,226,255,186,190,254,64,255,255,237,146,0,255,255,255,
248,255,186,170,165,255,16,0,0,255,16,0,0,255,16,0,0,255,255,255,240,255,186,170,
160,255,16,0,0,255,16,0,0,255,16,0,0,255,186,170,165,255,255,255,248,255,255,255,
248,255,186,170,165,255,16,0,0,255,16,0,0,255,16,0,0,255,186,170,160,255,255,255,
240,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,0,0,58,239,
215,16,0,6,255,203,239,193,0,47,244,0,28,249,0,159,128,0,3,152,0,223,48,0,
0,0,0,239,16,6,170,170,16,239,16,8,255,255,16,223,48,0,1,255,0,159,128,0,
4,252,0,47,244,0,28,246,0,6,255,203,239,176,0,0,74,238,199,0,0,255,16,0,
1,255,255,16,0,1,255,255,16,0,1,255,255,16,0,1,255,255,16,0,1,255,255,255,
255,255,255,255,186,170,171,255,255,16,0,1,255,255,16,0,1,255,255,16,0,1,255,255,
16,0,1,255,255,16,0,1,255,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,
16,255,16,255,16,255,16,255,16,0,0,15,241,0,0,15,241,0,0,15,241,0,0,15,
241,0,0,15,241,0,0,15,241,0,0,15,241,68,0,15,241,255,16,15,240,207,80,79,
208,111,235,239,112,6,222,215,0,255,16,0,127,227,255,16,5,255,64,255,16,62,246,0,
255,18,223,128,0,255,28,250,0,0,255,159,245,0,0,255,255,254,16,0,255,209,191,160,
0,255,48,46,245,0,255,16,7,254,16,255,16,0,207,160,255,16,0,47,245,255,16,0,
0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,
0,255,16,0,0,255,16,0,0,255,16,0,0,255,204,204,198,255,255,255,248,255,244,0,
0,63,255,16,255,250,0,0,143,255,16,255,222,0,0,221,255,16,255,159,80,3,249,255,
16,255,95,160,8,244,255,16,255,30,225,13,241,255,16,255,25,245,63,160,255,16,255,20,
249,127,96,255,16,255,16,236,191,16,255,16,255,16,159,251,0,255,16,255,16,79,246,0,
255,16,255,16,14,241,0,255,16,255,176,0,1,255,255,245,0,1,255,255,254,16,1,255,
255,143,144,1,255,255,46,243,1,255,255,22,252,1,255,255,16,207,113,255,255,16,63,226,
255,255,16,8,250,255,255,16,1,223,255,255,16,0,95,255,255,16,0,10,255,0,41,223,
217,32,0,5,239,219,223,229,0,46,246,0,6,254,32,143,144,0,0,159,128,223,64,0,
0,79,208,239,32,0,0,47,224,239,32,0,0,47,224,223,64,0,0,79,208,143,144,0,
0,159,128,46,246,0,6,254,32,5,239,219,223,229,0,0,57,223,217,48,0,255,255,236,
64,0,255,186,207,245,0,255,16,7,252,0,255,16,1,255,0,255,16,2,255,0,255,16,
8,252,0,255,186,207,245,0,255,255,236,80,0,255,16,0,0,0,255,16,0,0,0,255,
16,0,0,0,255,16,0,0,0,0,41,223,217,32,0,5,239,219,223,229,0,46,246,0,
6,254,32,143,144,0,0,159,128,223,48,0,0,79,208,239,16,0,0,31,224,239,32,0,
0,47,224,223,64,2,32,79,208,143,176,11,244,191,128,46,249,17,255,254,32,5,239,254,
255,245,0,0,57,223,223,248,0,0,0,0,0,175,80,255,255,236,80,0,255,186,207,245,
0,255,16,7,253,0,255,16,1,255,0,255,16,2,255,0,255,16,26,251,0,255,255,255,
227,0,255,187,255,64,0,255,16,143,144,0,255,16,30,242,0,255,16,8,250,0,255,16,
1,239,48,0,75,238,197,0,5,255,204,255,96,13,245,0,127,208,14,242,0,21,80,12,
252,64,0,0,4,239,254,162,0,0,39,207,255,64,0,0,2,191,192,73,112,0,47,240,
95,243,0,111,192,12,255,188,255,80,1,141,254,180,0,255,255,255,255,240,170,171,255,170,
160,0,1,255,0,0,0,1,255,0,0,0,1,255,0,0,0,1,255,0,0,0,1,255,
0,0,0,1,255,0,0,0,1,255,0,0,0,1,255,0,0,0,1,255,0,0,0,1,
255,0,0,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,
16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,239,48,0,47,240,
159,176,0,175,160,29,253,189,254,32,1,157,253,146,0,127,192,0,0,63,241,47,242,0,
0,143,176,12,247,0,0,223,80,6,252,0,3,255,16,1,255,32,8,250,0,0,191,112,
12,245,0,0,111,192,47,225,0,0,31,241,111,160,0,0,11,245,191,80,0,0,5,250,
238,16,0,0,1,239,250,0,0,0,0,175,245,0,0,143,176,0,31,245,0,7,252,79,
224,0,95,249,0,11,248,14,243,0,159,253,0,14,244,11,246,0,222,191,16,63,224,7,
250,1,251,143,80,111,176,3,253,5,248,95,144,175,112,0,239,41,244,31,192,223,48,0,
175,92,241,12,242,254,0,0,111,143,192,8,248,250,0,0,47,223,128,4,253,246,0,0,
13,255,64,1,255,242,0,0,10,255,16,0,207,208,0,46,245,0,0,223,112,6,254,16,
8,252,0,0,191,160,63,243,0,0,46,244,207,128,0,0,7,255,253,0,0,0,0,255,
244,0,0,0,1,255,247,0,0,0,10,253,255,48,0,0,95,226,175,192,0,1,239,96,
30,247,0,10,251,0,5,255,32,111,225,0,0,175,192,30,246,0,0,111,225,6,254,16,
0,223,112,0,207,128,7,253,0,0,79,225,30,244,0,0,10,249,127,176,0,0,2,239,
255,32,0,0,0,143,248,0,0,0,0,31,241,0,0,0,0,15,241,0,0,0,0,15,
241,0,0,0,0,15,241,0,0,0,0,15,241,0,0,255,255,255,255,240,170,170,170,255,
192,0,0,3,255,48,0,0,29,247,0,0,0,175,176,0,0,5,254,32,0,0,46,245,
0,0,0,191,144,0,0,7,253,16,0,0,63,227,0,0,0,223,250,170,170,160,255,255,
255,255,240,255,248,255,181,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,
16,255,16,255,16,255,181,255,248,88,16,0,127,64,0,79,128,0,14,192,0,11,241,0,
7,245,0,3,249,0,0,237,0,0,175,16,0,111,80,0,47,144,0,14,208,0,10,242,
0,6,246,0,2,250,143,255,16,90,255,16,0,255,16,0,255,16,0,255,16,0,255,16,
0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,90,255,16,
143,255,16,0,78,208,0,0,191,246,0,3,247,204,0,9,225,95,64,47,144,13,176,37,
16,3,80,170,170,170,170,255,255,255,255,34,0,95,112,9,225,5,207,216,16,95,252,207,
160,90,80,9,240,0,3,92,241,43,255,223,241,207,113,15,241,255,32,95,241,207,235,222,
241,43,252,78,241,255,16,0,0,0,255,16,0,0,0,255,16,0,0,0,255,59,236,80,
0,255,220,207,244,0,255,144,8,251,0,255,48,2,255,0,255,16,1,255,16,255,48,2,
255,0,255,144,8,251,0,255,220,207,244,0,255,43,237,80,0,2,174,235,64,46,253,191,
245,159,160,5,251,223,48,0,16,255,32,0,0,223,48,0,32,159,144,5,251,46,252,191,
245,2,174,235,64,0,0,0,255,16,0,0,0,255,16,0,0,0,255,16,4,207,195,255,
16,63,252,206,255,16,175,144,8,255,16,239,48,2,255,16,255,16,1,255,16,239,48,2,
255,16,175,144,8,255,16,63,252,205,239,16,4,206,195,239,16,2,174,235,48,0,46,252,
191,243,0,159,128,7,251,0,239,218,171,255,0,255,255,255,255,16,223,48,0,0,0,159,
128,4,183,0,46,252,191,246,0,2,174,252,96,0,0,3,119,16,0,127,255,32,0,239,
115,0,0,255,16,0,143,255,255,0,90,255,186,0,0,255,16,0,0,255,16,0,0,255,
16,0,0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,0,4,207,194,255,16,63,
252,205,255,16,175,144,8,255,16,239,48,2,255,16,255,16,1,255,16,239,48,2,255,16,
191,144,8,255,16,79,252,205,255,16,5,206,178,255,16,20,32,3,254,0,79,235,191,247,
0,5,206,252,96,0,255,16,0,0,255,16,0,0,255,16,0,0,255,59,238,128,255,236,
191,248,255,128,6,253,255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,255,16,
1,255,255,16,1,255,69,0,238,0,17,0,0,0,255,16,255,16,255,16,255,16,255,16,
255,16,255,16,255,16,255,16,0,69,0,0,238,0,0,17,0,0,0,0,0,255,16,0,
255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,1,
255,0,93,252,0,142,178,0,255,16,0,0,255,16,0,0,255,16,0,0,255,16,46,245,
255,16,207,128,255,25,251,0,255,127,209,0,255,255,176,0,255,223,245,0,255,21,254,32,
255,16,159,192,255,16,28,249,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,
255,16,255,16,255,16,255,16,255,92,234,21,223,178,0,255,219,239,174,190,251,0,255,96,
63,245,3,255,0,255,16,15,241,0,255,16,255,16,15,241,0,255,16,255,16,15,241,0,
255,16,255,16,15,241,0,255,16,255,16,15,241,0,255,16,255,16,15,241,0,255,16,255,
76,238,128,255,252,191,248,255,128,6,253,255,16,1,255,255,16,1,255,255,16,1,255,255,
16,1,255,255,16,1,255,255,16,1,255,2,174,235,48,0,46,252,207,227,0,159,144,8,
250,0,223,48,2,254,0,255,16,1,255,16,223,48,2,254,0,159,144,8,250,0,46,252,
207,227,0,2,174,235,48,0,255,43,236,80,0,255,220,207,244,0,255,144,8,251,0,255,
48,2,255,0,255,16,1,255,16,255,48,2,255,0,255,144,8,251,0,255,236,207,244,0,
255,60,236,80,0,255,16,0,0,0,255,16,0,0,0,255,16,0,0,0,4,206,178,239,
16,63,252,205,255,16,175,144,8,255,16,239,48,2,255,16,255,16,1,255,16,239,48,2,
255,16,175,144,8,255,16,63,252,205,255,16,4,206,194,255,16,0,0,0,255,16,0,0,
0,255,16,0,0,0,255,16,0,0,0,255,77,240,255,236,176,255,128,0,255,32,0,255,
16,0,255,16,0,255,16,0,255,16,0,255,16,0,1,158,253,112,0,11,254,190,247,0,
14,242,2,166,0,12,250,65,0,0,2,175,255,179,0,0,1,73,253,0,27,177,2,255,
0,12,253,190,250,0,1,158,253,129,0,0,255,16,0,255,16,143,255,248,90,255,181,0,
255,16,0,255,16,0,255,16,0,255,16,0,255,32,0,223,198,0,76,249,255,16,1,255,
255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,223,96,8,255,
143,252,205,255,8,238,162,255,143,144,0,79,208,47,224,0,159,112,12,244,0,239,32,7,
249,4,252,0,1,254,9,246,0,0,191,61,241,0,0,111,159,160,0,0,30,255,80,0,
0,10,254,0,0,143,144,6,252,0,79,208,79,192,10,255,16,127,144,14,241,13,255,64,
191,80,10,245,47,143,128,239,16,6,248,111,61,179,251,0,1,251,158,9,230,247,0,0,
206,218,5,252,242,0,0,143,246,1,255,208,0,0,63,242,0,191,144,0,13,245,0,191,
112,4,253,5,252,0,0,175,109,243,0,0,31,255,128,0,0,15,255,32,0,0,63,255,
160,0,0,207,76,245,0,7,250,3,253,16,46,242,0,159,144,143,144,0,79,208,47,224,
0,159,112,11,244,0,239,32,6,250,5,251,0,1,238,10,245,0,0,175,78,224,0,0,
79,207,128,0,0,13,255,48,0,0,8,252,0,0,0,10,246,0,0,7,191,209,0,0,
12,235,48,0,0,255,255,255,240,170,170,223,208,0,2,239,48,0,12,247,0,0,159,176,
0,5,254,16,0,46,244,0,0,207,234,170,160,255,255,255,240,0,2,100,0,111,248,0,
223,130,1,255,16,1,255,0,1,255,0,5,254,0,255,249,0,255,144,0,24,250,0,2,
255,0,1,255,0,1,255,0,0,239,80,0,143,248,0,5,149,136,0,255,16,255,16,255,
16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,
16,255,16,255,16,255,16,255,16,70,32,0,143,246,0,41,253,0,1,255,16,0,255,16,
0,255,16,0,239,64,0,159,255,0,10,255,0,175,129,0,255,32,0,255,16,0,255,16,
5,254,0,143,248,0,89,80,0,8,147,0,102,143,254,68,254,223,85,255,250,170,16,94,
210,171,0,255,0,137,0,0,0,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,
16,0,0,224,0,0,0,73,248,32,0,10,255,255,245,0,111,211,245,254,16,207,80,224,
101,16,239,32,224,0,0,239,32,224,0,0,207,80,224,102,16,111,211,245,254,16,10,255,
255,245,0,0,73,248,32,0,0,0,224,0,0,0,76,238,145,0,4,255,205,252,0,12,
246,0,207,64,14,242,0,52,16,15,241,0,0,0,175,251,170,48,0,255,255,255,80,0,
15,241,0,0,0,15,241,0,0,0,47,192,0,92,96,255,220,204,239,80,255,255,255,234,
0,1,0,0,0,1,0,29,56,223,214,76,16,46,255,219,223,252,16,12,244,0,6,247,
0,14,96,0,0,155,0,47,16,0,0,78,0,47,16,0,0,78,0,14,96,0,0,155,
0,14,244,0,6,249,0,63,255,203,223,254,16,8,24,223,214,39,0,30,243,0,31,242,
8,249,0,127,144,1,238,16,223,32,0,159,117,250,0,0,47,220,243,0,4,143,255,248,
48,8,255,255,255,112,0,0,255,16,0,8,255,255,255,112,5,170,255,186,80,0,0,255,
16,0,0,0,255,16,0,255,16,255,16,255,16,255,16,255,16,0,0,0,0,0,0,255,
16,255,16,255,16,255,16,255,16,255,16,1,174,234,16,0,10,253,207,160,0,14,242,13,
192,0,13,245,0,0,0,4,239,163,0,0,9,204,255,145,0,111,64,93,250,0,111,112,
2,255,0,30,250,51,252,0,2,191,254,113,0,0,4,223,144,0,2,48,47,240,0,31,
226,79,240,0,9,255,255,128,0,0,106,149,0,0,120,0,135,204,0,204,0,6,206,236,
112,0,0,2,207,251,190,252,32,0,12,251,73,165,175,209,0,127,212,255,255,108,247,0,
207,92,249,24,228,253,0,239,47,242,0,17,255,0,239,45,245,5,161,255,0,207,87,255,
191,132,253,0,127,209,126,233,28,247,0,12,251,16,1,191,209,0,2,207,251,190,252,32,
0,0,6,206,236,112,0,0,25,222,178,127,117,220,0,36,207,77,253,239,237,32,191,237,
87,255,77,251,175,0,9,248,62,226,0,143,162,239,48,6,252,45,245,0,31,243,159,160,
0,6,252,45,245,0,0,143,162,239,48,0,9,248,62,226,170,170,170,161,255,255,255,241,
0,0,15,241,0,0,15,241,0,125,253,112,0,11,163,3,171,0,122,47,253,42,112,211,
46,13,99,208,240,47,255,16,240,211,46,30,35,208,122,23,5,74,112,11,163,3,171,0,
0,125,253,112,0,170,170,255,255,6,166,0,127,255,96,239,86,208,239,36,224,175,221,160,
27,251,16,0,31,240,0,0,31,240,0,255,255,255,255,170,191,250,170,0,31,240,0,0,
31,240,0,0,0,0,0,255,255,255,255,170,170,170,170,59,238,161,221,87,249,17,5,247,
1,175,128,110,230,66,255,255,250,92,238,178,136,71,249,0,255,177,0,55,248,204,87,249,
59,237,145,2,33,30,192,126,32,255,16,1,255,0,255,16,1,255,0,255,16,1,255,0,
255,16,1,255,0,255,16,1,255,0,255,32,2,255,0,255,128,8,255,0,255,252,207,255,
192,255,109,230,93,240,255,16,0,0,0,255,16,0,0,0,255,16,0,0,0,4,190,255,
255,79,255,251,255,207,255,241,255,239,255,241,255,239,255,241,255,159,255,241,255,27,255,241,
255,0,71,129,255,0,0,1,255,0,0,1,255,0,0,1,255,0,0,1,255,137,0,255,
0,171,0,9,0,15,145,1,215,94,195,43,250,121,250,16,250,0,250,0,250,0,250,7,
223,196,0,127,132,207,32,221,0,63,128,251,0,31,160,221,0,63,128,127,132,207,32,7,
223,196,0,45,244,143,160,0,3,238,41,249,0,0,79,210,191,112,0,9,250,47,242,0,
79,210,191,112,3,238,41,249,0,45,244,143,160,0,27,255,16,0,7,208,0,121,255,16,
0,62,48,0,16,255,16,0,200,0,0,0,255,16,9,209,0,0,0,255,16,79,64,0,
0,0,255,17,233,0,188,0,0,255,26,209,5,255,0,0,0,111,64,30,175,0,0,2,
233,0,156,63,0,0,12,209,1,252,223,128,0,143,80,2,255,255,192,4,250,0,0,0,
63,0,7,187,16,0,4,177,0,109,255,16,0,30,128,0,82,255,16,0,157,0,0,0,
255,16,4,244,0,0,0,255,16,29,160,0,0,0,255,16,158,16,0,0,0,255,20,246,
6,238,96,0,68,29,176,13,136,224,0,0,159,32,0,5,208,0,4,248,0,0,45,80,
0,29,208,0,1,216,0,0,159,64,0,12,251,160,4,250,0,0,15,255,240,7,238,64,
0,0,124,0,44,85,208,0,2,227,0,0,6,208,0,12,112,0,0,175,129,0,124,0,
0,1,6,251,3,226,0,0,63,205,250,12,112,10,176,2,120,96,139,0,95,240,0,0,
4,226,1,218,240,0,0,29,96,8,195,240,0,0,155,0,31,205,248,0,4,226,0,31,
255,252,0,29,80,0,0,3,240,0,11,160,0,0,15,240,0,0,9,128,0,0,0,0,
0,0,47,240,0,0,127,192,0,9,254,64,0,159,194,0,0,239,32,4,66,239,64,47,
246,143,235,239,209,7,223,234,32,0,0,34,0,0,0,0,0,206,16,0,0,0,0,46,
112,0,0,0,0,0,0,0,0,0,0,143,247,0,0,0,0,223,252,0,0,0,3,251,
223,32,0,0,8,247,159,112,0,0,13,243,95,192,0,0,63,224,31,242,0,0,143,160,
11,247,0,0,223,251,191,253,0,3,255,255,255,255,48,8,250,0,0,191,128,13,246,0,
0,111,208,63,241,0,0,31,243,0,0,0,34,0,0,0,0,1,236,0,0,0,0,8,
226,0,0,0,0,0,0,0,0,0,0,143,247,0,0,0,0,223,252,0,0,0,3,251,
223,32,0,0,8,247,159,112,0,0,13,243,95,192,0,0,63,224,31,242,0,0,143,160,
11,247,0,0,223,251,191,253,0,3,255,255,255,255,48,8,250,0,0,191,128,13,246,0,
0,111,208,63,241,0,0,31,243,0,0,23,112,0,0,0,0,174,233,0,0,0,8,244,
79,112,0,0,0,0,0,0,0,0,0,143,247,0,0,0,0,223,252,0,0,0,3,251,
223,32,0,0,8,247,159,112,0,0,13,243,95,192,0,0,63,224,31,242,0,0,143,160,
11,247,0,0,223,251,191,253,0,3,255,255,255,255,48,8,250,0,0,191,128,13,246,0,
0,111,208,63,241,0,0,31,243,0,0,16,1,16,0,0,2,238,124,96,0,0,7,199,
238,32,0,0,1,16,0,0,0,0,0,143,247,0,0,0,0,223,252,0,0,0,3,251,
223,32,0,0,8,247,159,112,0,0,13,243,95,192,0,0,63,224,31,242,0,0,143,160,
11,247,0,0,223,251,191,253,0,3,255,255,255,255,48,8,250,0,0,191,128,13,246,0,
0,111,208,63,241,0,0,31,243,0,12,192,12,192,0,0,7,128,8,112,0,0,0,0,
0,0,0,0,0,207,251,0,0,0,2,255,255,16,0,0,6,253,239,96,0,0,11,249,
175,160,0,0,31,245,111,225,0,0,95,241,47,245,0,0,175,160,12,250,0,0,239,251,
191,254,0,4,255,255,255,255,64,9,251,0,0,191,144,14,246,0,0,111,224,63,241,0,
0,31,243,0,0,78,228,0,0,0,0,211,62,0,0,0,0,211,62,0,0,0,0,78,
228,0,0,0,0,0,0,0,0,0,0,143,247,0,0,0,0,223,252,0,0,0,3,252,
223,32,0,0,8,248,159,112,0,0,13,244,95,208,0,0,63,224,31,243,0,0,143,160,
11,248,0,0,223,251,191,253,0,3,255,255,255,255,48,8,251,0,0,191,128,13,246,0,
0,111,208,63,241,0,0,31,243,0,0,5,255,255,255,255,248,0,0,11,248,255,204,204,
198,0,0,63,208,255,16,0,0,0,0,159,112,255,16,0,0,0,1,239,32,255,16,0,
0,0,7,250,0,255,186,170,160,0,13,244,0,255,255,255,240,0,95,250,170,255,16,0,
0,0,191,255,255,255,16,0,0,2,255,16,0,255,16,0,0,9,250,0,0,255,186,170,
165,30,244,0,0,255,255,255,248,0,58,239,215,0,6,255,203,239,192,47,245,0,28,248,
159,128,0,4,254,223,48,0,0,0,239,16,0,0,0,239,16,0,0,0,223,48,0,0,
0,159,144,0,4,253,47,245,0,28,248,6,255,203,239,176,0,74,255,215,0,0,0,107,
48,0,0,0,73,224,0,0,0,190,144,0,1,33,0,0,2,235,0,0,0,95,48,0,
0,0,0,0,255,255,255,248,255,186,170,165,255,16,0,0,255,16,0,0,255,16,0,0,
255,255,255,240,255,186,170,160,255,16,0,0,255,16,0,0,255,16,0,0,255,186,170,165,
255,255,255,248,0,0,34,0,0,4,247,0,0,11,192,0,0,0,0,0,255,255,255,248,
255,186,170,165,255,16,0,0,255,16,0,0,255,16,0,0,255,255,255,240,255,186,170,160,
255,16,0,0,255,16,0,0,255,16,0,0,255,186,170,165,255,255,255,248,0,40,80,0,
1,221,245,0,11,226,158,48,0,0,0,0,255,255,255,248,255,186,170,165,255,16,0,0,
255,16,0,0,255,16,0,0,255,255,255,240,255,186,170,160,255,16,0,0,255,16,0,0,
255,16,0,0,255,186,170,165,255,255,255,248,12,192,12,176,7,112,8,112,0,0,0,0,
255,255,255,248,255,186,170,165,255,16,0,0,255,16,0,0,255,16,0,0,255,255,255,240,
255,186,170,160,255,16,0,0,255,16,0,0,255,16,0,0,255,186,170,165,255,255,255,248,
2,32,0,12,225,0,2,232,0,0,0,0,0,255,16,0,255,16,0,255,16,0,255,16,
0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,
2,33,30,192,126,32,0,0,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,16,
255,16,255,16,255,16,255,16,0,119,16,10,238,160,127,84,248,0,0,0,0,255,16,0,
255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,
255,16,0,255,16,0,255,16,204,0,203,120,0,135,0,0,0,1,255,0,1,255,0,1,
255,0,1,255,0,1,255,0,1,255,0,1,255,0,1,255,0,1,255,0,1,255,0,1,
255,0,1,255,0,0,255,255,253,146,0,0,255,186,190,254,80,0,255,16,0,111,226,0,
255,16,0,9,248,0,255,16,0,4,253,90,255,186,160,2,254,143,255,255,224,2,254,0,
255,16,0,4,253,0,255,16,0,9,248,0,255,16,0,111,226,0,255,186,190,254,64,0,
255,255,237,146,0,0,1,0,17,0,0,62,231,201,0,0,140,126,227,0,0,17,0,16,
0,255,176,0,1,255,255,245,0,1,255,255,254,16,1,255,255,143,144,1,255,255,46,243,
1,255,255,22,252,1,255,255,16,207,113,255,255,16,63,226,255,255,16,8,250,255,255,16,
1,223,255,255,16,0,95,255,255,16,0,10,255,0,2,32,0,0,0,0,7,247,0,0,
0,0,0,174,16,0,0,0,0,0,0,0,0,0,41,223,217,32,0,5,239,219,223,229,
0,46,246,0,6,254,32,143,144,0,0,159,128,223,64,0,0,79,208,239,32,0,0,47,
224,239,32,0,0,47,224,223,64,0,0,79,208,143,144,0,0,159,128,46,246,0,6,254,
32,5,239,219,223,229,0,0,57,223,217,48,0,0,0,0,34,0,0,0,0,7,247,0,
0,0,0,30,160,0,0,0,0,0,0,0,0,0,41,223,217,32,0,5,239,219,223,229,
0,46,246,0,6,254,32,143,144,0,0,159,128,223,64,0,0,79,208,239,32,0,0,47,
224,239,32,0,0,47,224,223,64,0,0,79,208,143,144,0,0,159,128,46,246,0,6,254,
32,5,239,219,223,229,0,0,57,223,217,48,0,0,0,88,80,0,0,0,8,249,247,0,
0,0,56,64,72,32,0,0,0,0,0,0,0,0,41,223,217,32,0,5,239,219,223,229,
0,46,246,0,6,254,32,143,144,0,0,159,128,223,64,0,0,79,208,239,32,0,0,47,
224,239,32,0,0,47,224,223,64,0,0,79,208,143,144,0,0,159,128,46,246,0,6,254,
32,5,239,219,223,229,0,0,57,223,217,48,0,0,0,16,1,0,0,0,11,251,111,32,
0,0,47,123,251,0,0,0,1,0,16,0,0,0,41,223,217,32,0,5,239,219,223,229,
0,46,246,0,6,254,32,143,144,0,0,159,128,223,64,0,0,79,208,239,32,0,0,47,
224,239,32,0,0,47,224,223,64,0,0,79,208,143,144,0,0,159,128,46,246,0,6,254,
32,5,239,219,223,229,0,0,57,223,217,48,0,0,8,112,119,0,0,0,12,176,204,0,
0,0,0,0,0,0,0,0,41,223,217,32,0,5,239,219,223,229,0,46,246,0,6,254,
32,143,144,0,0,159,128,223,64,0,0,79,208,239,32,0,0,47,224,239,32,0,0,47,
224,223,64,0,0,79,208,143,144,0,0,159,128,46,246,0,6,254,32,5,239,219,223,229,
0,0,57,223,217,48,0,7,32,0,69,0,95,209,3,239,32,9,252,78,246,0,0,175,
255,96,0,0,63,255,16,0,2,239,255,193,0,46,247,11,251,16,62,128,0,188,16,2,
0,0,1,0,0,0,0,0,2,0,0,41,223,217,111,48,5,239,219,223,255,0,46,246,
0,15,255,32,143,144,0,127,223,128,223,64,3,247,79,208,239,32,29,176,47,224,239,32,
174,32,47,224,223,71,244,0,79,208,143,207,128,0,159,128,47,255,0,6,254,32,15,255,
219,223,229,0,63,137,223,217,48,0,2,0,0,0,0,0,0,18,16,0,0,0,63,128,
0,0,0,7,225,0,0,0,0,0,0,0,255,16,0,15,241,255,16,0,15,241,255,16,
0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,
16,0,15,241,239,48,0,47,240,159,176,0,175,160,29,253,189,254,32,1,157,253,146,0,
0,0,2,16,0,0,0,127,64,0,0,1,232,0,0,0,0,0,0,0,255,16,0,15,
241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,
15,241,255,16,0,15,241,255,16,0,15,241,239,48,0,47,240,159,176,0,175,160,29,253,
189,254,32,1,157,253,146,0,0,4,132,0,0,0,79,159,80,0,0,117,5,113,0,0,
0,0,0,0,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,
255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,239,64,0,63,
240,159,194,2,207,160,29,255,239,254,32,1,157,253,146,0,0,120,7,128,0,0,204,12,
192,0,0,0,0,0,0,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,
0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,255,16,0,15,241,239,
48,0,47,240,159,176,0,175,160,29,253,189,254,32,1,157,253,146,0,0,0,0,34,16,
0,0,0,1,236,0,0,0,0,8,226,0,0,0,0,0,0,0,0,30,246,0,0,111,
225,6,254,16,0,223,112,0,207,128,7,253,0,0,79,225,30,244,0,0,10,249,127,176,
0,0,2,239,255,32,0,0,0,143,248,0,0,0,0,31,241,0,0,0,0,15,241,0,
0,0,0,15,241,0,0,0,0,15,241,0,0,0,0,15,241,0,0,255,16,0,0,0,
255,16,0,0,0,255,203,167,16,0,255,255,255,226,0,255,16,26,251,0,255,16,2,255,
0,255,16,2,255,0,255,16,42,251,0,255,255,255,226,0,255,186,167,16,0,255,16,0,
0,0,255,16,0,0,0,6,206,236,80,0,127,252,191,247,0,223,96,5,254,0,255,16,
2,254,0,255,16,25,247,0,255,24,255,112,0,255,21,191,246,0,255,16,5,253,0,255,
16,1,255,0,255,16,4,254,0,255,21,190,248,0,255,24,254,128,0,1,33,0,0,1,
218,0,0,0,79,32,0,0,0,0,0,5,207,216,16,95,252,207,160,90,80,9,240,0,
3,92,241,43,255,223,241,207,113,15,241,255,32,95,241,207,235,222,241,43,252,78,241,0,
0,34,0,0,5,245,0,0,11,144,0,0,0,0,0,5,207,216,16,95,252,207,160,90,
80,9,240,0,3,92,241,43,255,223,241,207,113,15,241,255,32,95,241,207,235,222,241,43,
252,78,241,0,40,80,0,2,218,230,0,6,112,71,16,0,0,0,0,5,207,216,16,95,
252,207,160,90,80,9,240,0,3,92,241,43,255,223,241,207,113,15,241,255,32,95,241,207,
235,222,241,43,252,78,241,0,16,1,0,3,252,126,0,8,185,249,0,1,16,16,0,5,
207,216,16,95,252,207,160,90,80,9,240,0,3,92,241,43,255,223,241,207,113,15,241,255,
32,95,241,207,235,222,241,43,252,78,241,4,80,5,64,14,224,14,208,1,16,1,16,0,
0,0,0,5,207,216,16,95,252,207,160,90,80,10,240,0,19,92,241,43,255,223,241,207,
113,15,241,255,32,95,241,207,235,238,241,43,253,77,241,0,77,212,0,0,227,62,0,0,
227,78,0,0,78,228,0,0,0,0,0,5,207,216,16,79,252,207,160,89,80,9,240,0,
3,91,241,42,255,223,241,207,113,15,241,255,32,95,241,207,235,238,241,43,253,77,241,5,
207,234,39,223,215,0,95,252,207,255,219,239,144,55,64,9,252,16,29,243,2,122,175,255,
170,175,246,95,255,255,255,255,255,247,223,129,7,246,0,0,0,239,32,28,252,16,9,163,
191,235,239,207,235,223,192,26,238,180,6,223,233,16,2,174,235,64,46,252,191,244,159,144,
6,249,223,48,0,16,255,16,0,0,223,48,0,32,159,144,6,250,46,252,191,244,2,175,
251,64,0,7,180,0,0,3,158,0,0,11,233,0,0,34,0,0,0,0,190,16,0,0,
0,30,128,0,0,0,0,0,0,0,2,174,235,48,0,46,252,191,243,0,159,128,7,251,
0,239,218,171,255,0,255,255,255,255,16,223,48,0,0,0,159,128,4,183,0,46,252,191,
246,0,2,174,252,96,0,0,0,34,0,0,0,1,235,0,0,0,7,226,0,0,0,0,
0,0,0,2,174,235,48,0,46,252,191,243,0,159,128,7,251,0,239,218,171,255,0,255,
255,255,255,16,223,48,0,0,0,159,128,4,183,0,46,252,191,246,0,2,174,252,96,0,
0,23,113,0,0,1,204,204,16,0,5,113,23,80,0,0,0,0,0,0,2,174,235,48,
0,46,252,191,243,0,159,128,7,251,0,239,218,171,255,0,255,255,255,255,16,223,48,0,
0,0,159,128,4,183,0,46,252,191,246,0,2,174,252,96,0,4,80,5,64,0,14,224,
14,208,0,1,16,1,16,0,0,0,0,0,0,2,174,235,48,0,46,252,191,243,0,159,
128,7,251,0,255,218,171,255,0,255,255,255,255,16,223,32,0,0,0,159,128,4,183,0,
46,252,191,247,0,2,174,252,96,0,18,32,0,12,225,0,2,232,0,0,0,0,0,255,
16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,
16,2,33,30,209,126,48,0,0,255,16,255,16,255,16,255,16,255,16,255,16,255,16,255,
16,255,16,0,119,16,10,238,176,143,68,248,0,0,0,0,255,16,0,255,16,0,255,16,
0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,0,255,16,69,0,84,238,0,237,
17,0,17,0,0,0,1,255,0,1,255,0,1,255,0,1,255,0,1,255,0,1,255,0,
1,255,0,1,255,0,1,255,0,2,180,3,64,0,2,223,254,80,0,1,159,252,0,0,
5,146,175,128,0,1,138,108,242,0,29,255,252,249,0,143,194,44,253,0,223,64,4,255,
0,255,32,1,255,0,239,48,2,254,0,175,144,8,250,0,46,252,207,226,0,2,174,235,
48,0,0,16,1,16,4,254,123,160,10,183,239,64,1,16,1,0,255,76,238,128,255,252,
191,248,255,128,6,253,255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,255,16,
1,255,255,16,1,255,0,34,0,0,0,0,174,16,0,0,0,30,112,0,0,0,0,0,
0,0,2,174,235,48,0,46,252,207,227,0,159,144,8,250,0,223,48,2,254,0,255,16,
1,255,16,223,48,2,254,0,159,144,8,250,0,46,252,207,227,0,2,174,235,48,0,0,
0,34,0,0,0,1,235,0,0,0,7,226,0,0,0,0,0,0,0,2,174,235,48,0,
46,252,207,227,0,159,144,8,250,0,223,48,2,254,0,255,16,1,255,16,223,48,2,254,
0,159,144,8,250,0,46,252,207,227,0,2,174,235,48,0,0,7,113,0,0,1,188,203,
16,0,4,114,23,64,0,0,0,0,0,0,2,174,235,48,0,46,252,207,227,0,159,144,
8,250,0,223,48,2,254,0,255,16,1,255,16,223,48,2,254,0,159,144,8,250,0,46,
252,207,227,0,2,174,235,48,0,0,0,1,0,0,1,222,125,96,0,5,215,238,16,0,
0,16,0,0,0,2,174,235,48,0,46,252,207,227,0,159,144,8,250,0,223,48,2,254,
0,255,16,1,255,16,223,48,2,254,0,159,144,8,250,0,46,252,207,227,0,2,174,235,
48,0,4,80,5,64,0,14,224,14,224,0,1,16,1,16,0,0,0,0,0,0,2,174,
235,48,0,46,252,207,227,0,159,144,8,250,0,223,48,2,254,0,255,16,1,255,16,223,
48,2,254,0,159,144,8,250,0,46,252,207,227,0,2,174,235,48,0,0,11,160,0,0,
15,224,0,0,9,128,0,0,0,0,0,255,255,255,255,170,170,170,170,0,9,128,0,0,
15,224,0,0,11,160,0,0,0,0,0,16,0,42,238,184,208,2,239,204,255,96,9,249,
2,255,160,13,243,29,175,224,15,242,186,31,241,13,253,192,47,224,9,255,16,143,160,7,
255,204,254,48,46,106,238,179,0,2,0,0,0,0,1,34,0,0,1,222,16,0,0,63,
128,0,0,0,0,0,255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,255,16,
1,255,255,16,1,255,223,96,8,255,143,252,205,255,8,238,162,255,0,0,34,16,0,1,
237,16,0,9,243,0,0,0,0,0,255,16,1,255,255,16,1,255,255,16,1,255,255,16,
1,255,255,16,1,255,255,16,1,255,223,96,8,255,143,252,205,255,8,238,162,255,0,23,
113,0,1,206,235,0,10,243,63,160,0,0,0,0,255,16,1,255,255,16,1,255,255,16,
1,255,255,16,1,255,255,16,1,255,255,16,2,255,223,112,9,255,143,253,221,255,8,238,
162,255,4,80,5,64,14,224,14,224,1,16,1,16,0,0,0,0,255,16,1,255,255,16,
1,255,255,16,1,255,255,16,1,255,255,16,1,255,255,16,1,255,223,96,8,255,143,252,
205,255,8,238,162,255,0,0,79,144,0,0,0,204,16,0,0,0,33,0,0,0,0,0,
0,0,143,144,0,79,208,47,224,0,159,112,11,244,0,239,32,6,250,5,251,0,1,238,
10,245,0,0,175,78,224,0,0,79,207,128,0,0,13,255,48,0,0,8,252,0,0,0,
10,246,0,0,7,191,209,0,0,12,235,48,0,0,255,16,0,0,0,255,16,0,0,0,
255,16,0,0,0,255,59,236,80,0,255,220,207,244,0,255,144,8,251,0,255,48,2,255,
0,255,16,1,255,16,255,48,2,255,0,255,144,8,251,0,255,236,207,244,0,255,60,236,
80,0,255,16,0,0,0,255,16,0,0,0,255,16,0,0,0,0,69,0,84,0,0,238,
0,237,0,0,17,0,17,0,0,0,0,0,0,111,176,0,11,246,31,242,0,47,225,10,
248,0,143,144,5,253,0,239,48,0,239,69,252,0,0,159,154,246,0,0,63,255,225,0,
0,13,255,144,0,0,7,255,48,0,0,10,252,0,0,6,191,244,0,0,11,252,80,0,
0,0,58,223,255,255,255,255,128,5,255,219,191,250,170,170,80,46,246,0,31,240,0,0,
0,143,144,0,31,240,0,0,0,223,64,0,31,240,0,0,0,239,32,0,31,255,255,255,
0,239,16,0,31,250,170,170,0,223,64,0,31,240,0,0,0,143,144,0,31,240,0,0,
0,46,245,0,31,240,0,0,0,5,255,219,191,250,170,170,80,0,58,223,255,255,255,255,
128,2,174,235,50,174,235,64,0,46,252,207,255,252,191,243,0,159,144,8,255,144,7,251,
0,223,48,2,255,218,171,255,0,255,16,1,255,255,255,255,16,223,48,2,255,32,0,0,
0,159,144,8,255,128,4,166,0,46,252,207,255,252,191,247,0,2,174,235,50,174,252,80,
0,0,127,55,243,0,0,11,207,112,0,0,2,185,0,0,0,0,0,0,0,0,75,238,
198,0,5,255,204,255,112,13,245,0,111,224,14,242,0,0,0,12,252,64,0,0,3,239,
254,162,0,0,22,191,255,64,0,0,1,159,192,92,160,0,47,240,79,245,0,143,192,11,
255,238,255,80,1,141,254,180,0,1,117,5,113,0,0,111,159,80,0,0,4,132,0,0,
0,0,0,0,0,1,158,253,112,0,11,254,190,247,0,14,242,2,166,0,12,250,65,0,
0,2,175,255,179,0,0,1,73,253,0,27,177,2,255,0,12,253,190,250,0,1,158,253,
129,0,0,12,192,12,192,0,0,7,128,8,112,0,0,0,0,0,0,0,63,243,0,0,
63,243,9,252,0,0,207,144,1,239,112,6,254,16,0,111,226,30,246,0,0,12,250,143,
176,0,0,3,255,255,32,0,0,0,159,248,0,0,0,0,47,241,0,0,0,0,31,240,
0,0,0,0,31,240,0,0,0,0,31,240,0,0,0,0,31,240,0,0,2,132,4,131,
0,0,174,94,160,0,0,11,252,16,0,0,0,0,0,0,255,255,255,255,240,170,170,170,
255,192,0,0,3,255,48,0,0,29,247,0,0,0,175,176,0,0,5,254,32,0,0,46,
245,0,0,0,191,144,0,0,7,253,16,0,0,63,227,0,0,0,223,250,170,170,160,255,
255,255,255,240,23,64,72,32,9,245,234,0,0,191,176,0,0,0,0,0,255,255,255,240,
170,170,223,208,0,2,239,48,0,28,247,0,0,159,176,0,6,253,16,0,62,244,0,0,
223,236,204,192,255,255,255,240,170,170,170,170,255,255,255,255,170,170,170,170,170,170,170,170,
255,255,255,255,255,255,255,255,9,208,13,176,47,144,111,112,175,80,63,208,95,128,127,64,
159,16,187,0,9,208,63,64,13,176,127,32,47,144,207,0,111,113,253,0,175,85,251,0,
63,208,239,16,95,129,252,0,127,67,248,0,159,21,244,0,187,7,241,0,27,251,16,191,
255,176,255,255,240,191,255,176,27,251,16,137,0,8,144,0,137,0,255,0,15,240,0,255,
0,171,0,10,176,0,171,0,0,9,248,0,143,160,6,252,16,31,243,0,6,252,16,0,
143,160,0,9,248,45,244,0,3,238,32,0,79,210,0,9,250,0,79,210,3,238,32,45,
244,0,
};
static const VGlyph F_BTN_glyphs[] = {
{32,0,0,0,0,0,64},
{33,0,3,12,1,-12,80},
{34,24,6,5,2,-12,128},
{35,39,11,12,0,-12,160},
{36,111,9,16,0,-14,160},
{37,191,14,12,2,-12,256},
{38,275,10,12,1,-12,176},
{39,335,3,5,2,-12,80},
{40,345,4,15,1,-13,96},
{41,375,5,15,0,-13,96},
{42,420,7,7,1,-12,144},
{43,448,8,8,1,-9,176},
{44,480,4,5,0,-2,80},
{45,490,5,2,1,-6,112},
{46,496,3,3,1,-3,80},
{47,502,6,15,0,-13,96},
{48,547,9,12,1,-12,176},
{49,607,6,12,1,-12,112},
{50,643,8,12,1,-12,160},
{51,691,9,12,1,-12,160},
{52,751,10,12,0,-12,176},
{53,811,9,12,1,-12,160},
{54,871,9,12,1,-12,160},
{55,931,8,12,1,-12,144},
{56,979,9,12,1,-12,160},
{57,1039,9,12,1,-12,160},
{58,1099,3,9,1,-9,80},
{59,1117,4,12,0,-9,80},
{60,1141,8,10,1,-10,176},
{61,1181,8,6,2,-8,176},
{62,1205,8,10,2,-10,176},
{63,1245,8,12,1,-12,144},
{64,1293,16,15,1,-12,256},
{65,1413,12,12,0,-12,192},
{66,1485,9,12,1,-12,176},
{67,1545,11,12,1,-12,192},
{68,1617,10,12,1,-12,192},
{69,1677,8,12,1,-12,160},
{70,1725,8,12,1,-12,144},
{71,1773,11,12,1,-12,192},
{72,1845,10,12,1,-12,192},
{73,1905,3,12,1,-12,64},
{74,1929,8,12,1,-12,144},
{75,1977,10,12,1,-12,176},
{76,2037,8,12,1,-12,144},
{77,2085,13,12,1,-12,240},
{78,2169,10,12,1,-12,192},
{79,2229,11,12,1,-12,192},
{80,2301,9,12,1,-12,160},
{81,2361,11,13,1,-12,192},
{82,2439,9,12,1,-12,160},
{83,2499,9,12,0,-12,160},
{84,2559,9,12,1,-12,176},
{85,2619,10,12,1,-12,192},
{86,2679,12,12,0,-12,192},
{87,2751,16,12,0,-12,256},
{88,2847,12,12,0,-12,176},
{89,2919,12,12,0,-12,176},
{90,2991,9,12,1,-12,160},
{91,3051,4,15,1,-13,96},
{92,3081,6,15,0,-13,96},
{93,3126,5,15,0,-13,96},
{94,3171,8,6,0,-12,128},
{95,3195,8,2,0,0,128},
{96,3203,4,3,1,-13,80},
{97,3209,8,9,1,-9,144},
{98,3245,9,12,1,-12,160},
{99,3305,8,9,1,-9,144},
{100,3341,9,12,1,-12,160},
{101,3401,9,9,1,-9,144},
{102,3446,7,13,0,-13,96},
{103,3498,9,12,1,-9,160},
{104,3558,8,12,1,-12,160},
{105,3606,3,13,1,-13,64},
{106,3632,5,16,-1,-13,64},
{107,3680,8,12,1,-12,144},
{108,3728,3,12,1,-12,64},
{109,3752,13,9,1,-9,224},
{110,3815,8,9,1,-9,160},
{111,3851,9,9,1,-9,160},
{112,3896,9,12,1,-9,160},
{113,3956,9,12,1,-9,160},
{114,4016,5,10,1,-10,96},
{115,4046,9,9,0,-9,144},
{116,4091,6,11,0,-11,96},
{117,4124,8,9,1,-9,160},
{118,4160,9,9,0,-9,144},
{119,4205,13,9,0,-9,208},
{120,4268,9,9,0,-9,144},
{121,4313,9,12,0,-9,144},
{122,4373,7,9,1,-9,144},
{123,4409,6,16,1,-13,112},
{124,4457,3,20,2,-16,96},
{125,4497,6,16,0,-13,112},
{126,4545,8,4,1,-7,176},
{160,4561,0,0,0,0,64},
{161,4561,3,12,1,-9,80},
{162,4585,9,12,1,-12,144},
{163,4645,9,12,1,-12,160},
{164,4705,11,11,0,-11,192},
{165,4771,10,12,-1,-12,144},
{166,4831,3,14,2,-12,80},
{167,4859,9,15,0,-12,144},
{168,4934,6,2,2,-12,160},
{169,4940,13,12,1,-12,240},
{170,5024,6,7,1,-12,112},
{171,5045,10,7,0,-9,160},
{172,5080,8,4,2,-6,176},
{173,5096,0,0,0,0,0},
{174,5096,9,9,1,-12,176},
{175,5141,4,2,1,-13,112},
{176,5145,5,6,1,-12,112},
{177,5163,8,9,1,-9,176},
{178,5199,6,6,1,-13,112},
{179,5217,6,6,1,-13,112},
{180,5235,4,3,1,-13,80},
{181,5241,10,12,1,-9,160},
{182,5301,8,12,1,-12,160},
{183,5349,3,3,1,-7,80},
{184,5355,4,4,1,-1,80},
{185,5363,4,6,0,-13,80},
{186,5375,7,7,1,-12,128},
{187,5403,10,7,0,-9,160},
{188,5438,13,12,0,-12,208},
{189,5522,13,13,0,-13,224},
{190,5613,14,12,0,-12,224},
{191,5697,8,12,1,-9,144},
{192,5745,12,16,0,-16,192},
{193,5841,12,16,0,-16,192},
{194,5937,12,16,0,-16,192},
{195,6033,12,16,0,-16,192},
{196,6129,12,15,0,-15,192},
{197,6219,12,17,0,-17,192},
{198,6321,16,12,0,-12,256},
{199,6417,10,15,1,-12,192},
{200,6492,8,16,1,-16,160},
{201,6556,8,16,1,-16,160},
{202,6620,8,16,1,-16,160},
{203,6684,8,15,1,-15,160},
{204,6744,5,16,-1,-16,64},
{205,6792,4,16,1,-16,64},
{206,6824,6,16,-1,-16,64},
{207,6872,6,15,0,-15,64},
{208,6917,12,12,0,-12,192},
{209,6989,10,16,1,-16,192},
{210,7069,11,16,1,-16,192},
{211,7165,11,16,1,-16,192},
{212,7261,11,16,1,-16,192},
{213,7357,11,16,1,-16,192},
{214,7453,11,15,1,-15,192},
{215,7543,9,9,1,-9,176},
{216,7588,11,14,1,-13,192},
{217,7672,10,16,1,-16,192},
{218,7752,10,16,1,-16,192},
{219,7832,10,16,1,-16,192},
{220,7912,10,15,1,-15,192},
{221,7987,12,16,0,-16,176},
{222,8083,9,12,1,-12,160},
{223,8143,9,12,1,-12,160},
{224,8203,8,13,1,-13,144},
{225,8255,8,13,1,-13,144},
{226,8307,8,13,1,-13,144},
{227,8359,8,13,1,-13,144},
{228,8411,8,13,1,-13,144},
{229,8463,8,14,1,-14,144},
{230,8519,14,9,1,-9,240},
{231,8582,8,12,1,-9,144},
{232,8630,9,13,1,-13,144},
{233,8695,9,13,1,-13,144},
{234,8760,9,13,1,-13,144},
{235,8825,9,13,1,-13,144},
{236,8890,5,13,-1,-13,64},
{237,8929,4,13,1,-13,64},
{238,8955,6,13,-1,-13,64},
{239,8994,6,13,-1,-13,64},
{240,9033,9,13,1,-13,144},
{241,9098,8,13,1,-13,160},
{242,9150,9,13,1,-13,160},
{243,9215,9,13,1,-13,160},
{244,9280,9,13,1,-13,160},
{245,9345,9,13,1,-13,160},
{246,9410,9,13,1,-13,160},
{247,9475,8,9,2,-9,176},
{248,9511,10,11,0,-10,160},
{249,9566,8,13,1,-13,160},
{250,9618,8,13,1,-13,160},
{251,9670,8,13,1,-13,160},
{252,9722,8,13,1,-13,160},
{253,9774,9,16,0,-13,144},
{254,9854,9,15,1,-12,160},
{255,9929,10,16,0,-13,144},
{338,10009,15,12,1,-12,256},
{339,10105,15,9,1,-9,256},
{352,10177,9,16,0,-16,160},
{353,10257,9,13,0,-13,144},
{376,10322,12,15,0,-15,176},
{381,10412,9,16,1,-16,160},
{382,10492,7,13,1,-13,144},
{8211,10544,8,2,0,-6,128},
{8212,10552,16,2,0,-6,256},
{8216,10568,3,5,1,-12,80},
{8217,10578,3,5,1,-12,80},
{8220,10588,7,5,1,-12,128},
{8221,10608,7,5,1,-12,128},
{8226,10628,5,5,1,-7,128},
{8230,10643,13,3,1,-3,240},
{8249,10664,6,7,0,-9,112},
{8250,10685,6,7,0,-9,112},
};
static const VFont F_BTN = { F_BTN_glyphs, 208, F_BTN_bmp, 16, 4 };
static const uint8_t F_CODE_bmp[] = {
0,0,0,4,156,239,236,148,0,0,0,0,0,0,4,223,255,255,255,255,212,0,0,0,
0,0,143,255,255,255,255,255,255,128,0,0,0,8,255,255,255,255,255,255,255,248,0,0,
0,95,255,255,231,50,56,239,255,255,80,0,1,239,255,252,32,0,0,44,255,255,225,0,
7,255,255,225,0,0,0,2,239,255,247,0,13,255,255,96,0,0,0,0,111,255,253,0,
63,255,254,0,0,0,0,0,30,255,255,48,127,255,250,0,0,0,0,0,10,255,255,112,
175,255,246,0,0,0,0,0,7,255,255,160,207,255,243,0,0,0,0,0,3,255,255,192,
239,255,242,0,0,0,0,0,2,255,255,208,239,255,241,0,0,0,0,0,1,255,255,224,
255,255,240,0,0,0,0,0,0,255,255,240,239,255,241,0,0,0,0,0,1,255,255,224,
239,255,242,0,0,0,0,0,2,255,255,208,207,255,243,0,0,0,0,0,3,255,255,192,
175,255,246,0,0,0,0,0,7,255,255,160,127,255,250,0,0,0,0,0,10,255,255,112,
63,255,254,0,0,0,0,0,30,255,255,48,13,255,255,96,0,0,0,0,111,255,253,0,
7,255,255,225,0,0,0,2,239,255,247,0,1,239,255,252,16,0,0,44,255,255,225,0,
0,95,255,255,231,50,55,239,255,255,80,0,0,8,255,255,255,255,255,255,255,248,0,0,
0,0,143,255,255,255,255,255,255,128,0,0,0,0,5,223,255,255,255,255,212,0,0,0,
0,0,0,5,157,239,236,148,0,0,0,0,0,0,0,143,255,255,0,0,77,255,255,255,
0,25,255,255,255,255,5,223,255,255,255,255,175,255,255,255,255,255,255,255,252,63,255,255,
255,254,112,15,255,255,255,178,0,15,255,255,230,0,0,15,255,255,16,0,0,15,255,255,
0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,
0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,
0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,
0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,
0,0,0,15,255,255,0,0,0,15,255,255,0,0,0,15,255,255,0,0,2,123,223,254,
200,48,0,0,0,1,143,255,255,255,255,251,32,0,0,44,255,255,255,255,255,255,228,0,
1,207,255,255,255,255,255,255,254,48,9,255,255,252,99,36,159,255,255,208,47,255,255,144,
0,0,5,255,255,246,127,255,252,0,0,0,0,159,255,251,191,255,246,0,0,0,0,63,
255,253,239,255,242,0,0,0,0,31,255,255,255,255,240,0,0,0,0,31,255,254,17,17,
16,0,0,0,0,63,255,252,0,0,0,0,0,0,0,143,255,248,0,0,0,0,0,0,
3,255,255,243,0,0,0,0,0,0,29,255,255,160,0,0,0,0,0,1,207,255,254,32,
0,0,0,0,0,45,255,255,244,0,0,0,0,0,3,223,255,255,80,0,0,0,0,0,
78,255,255,246,0,0,0,0,0,5,255,255,255,80,0,0,0,0,0,127,255,255,228,0,
0,0,0,0,9,255,255,253,48,0,0,0,0,1,191,255,255,194,0,0,0,0,0,28,
255,255,251,16,0,0,0,0,2,223,255,255,160,0,0,0,0,0,78,255,255,250,34,34,
34,34,34,34,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,56,
190,254,220,131,0,0,0,0,0,43,255,255,255,255,255,195,0,0,0,5,239,255,255,255,
255,255,255,112,0,0,79,255,255,255,255,255,255,255,247,0,0,223,255,255,165,34,73,255,
255,255,48,6,255,255,244,0,0,0,62,255,255,144,11,255,255,128,0,0,0,6,255,255,
208,14,255,255,32,0,0,0,1,255,255,224,10,170,170,0,0,0,0,1,255,255,224,0,
0,0,0,0,0,0,5,255,255,160,0,0,0,0,0,0,0,45,255,255,48,0,0,0,
0,18,35,89,255,255,247,0,0,0,0,0,207,255,255,255,254,80,0,0,0,0,0,207,
255,255,253,97,0,0,0,0,0,0,207,255,255,255,251,64,0,0,0,0,0,207,255,255,
255,255,248,0,0,0,0,0,0,1,38,207,255,255,144,0,0,0,0,0,0,0,6,255,
255,244,0,0,0,0,0,0,0,0,143,255,250,170,170,160,0,0,0,0,0,47,255,253,
239,255,242,0,0,0,0,0,15,255,255,191,255,246,0,0,0,0,0,63,255,254,127,255,
252,0,0,0,0,0,175,255,251,47,255,255,161,0,0,0,8,255,255,245,8,255,255,253,
132,34,71,223,255,255,192,0,191,255,255,255,255,255,255,255,254,32,0,26,255,255,255,255,
255,255,255,210,0,0,0,109,255,255,255,255,255,231,0,0,0,0,0,89,206,255,236,149,
16,0,0,0,0,0,0,0,0,12,255,255,255,0,0,0,0,0,0,0,0,143,255,255,
255,0,0,0,0,0,0,0,4,255,255,255,255,0,0,0,0,0,0,0,29,255,255,255,
255,0,0,0,0,0,0,0,175,255,255,255,255,0,0,0,0,0,0,6,255,255,159,255,
255,0,0,0,0,0,0,46,255,253,31,255,255,0,0,0,0,0,0,207,255,243,15,255,
255,0,0,0,0,0,9,255,255,112,15,255,255,0,0,0,0,0,79,255,251,0,15,255,
255,0,0,0,0,1,239,255,226,0,15,255,255,0,0,0,0,11,255,255,80,0,15,255,
255,0,0,0,0,127,255,249,0,0,15,255,255,0,0,0,3,255,255,209,0,0,15,255,
255,0,0,0,29,255,255,48,0,0,15,255,255,0,0,0,159,255,247,0,0,0,15,255,
255,0,0,5,255,255,176,0,0,0,15,255,255,0,0,46,255,254,16,0,0,0,15,255,
255,0,0,127,255,255,255,255,255,255,255,255,255,255,255,143,255,255,255,255,255,255,255,255,
255,255,255,143,255,255,255,255,255,255,255,255,255,255,255,143,255,255,255,255,255,255,255,255,
255,255,255,18,34,34,34,34,34,34,47,255,255,34,34,0,0,0,0,0,0,0,15,255,
255,0,0,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,0,15,255,
255,0,0,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,0,15,255,
255,0,0,0,0,0,0,0,0,0,15,255,255,0,0,15,255,255,255,255,255,255,255,255,
240,0,15,255,255,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,255,255,240,0,
15,255,255,255,255,255,255,255,255,240,0,15,255,254,34,34,34,34,34,34,32,0,15,255,
253,0,0,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,0,15,255,251,0,
0,0,0,0,0,0,0,15,255,250,0,0,0,0,0,0,0,0,15,255,248,1,123,239,
236,130,0,0,0,15,255,247,110,255,255,255,255,145,0,0,15,255,252,255,255,255,255,255,
252,32,0,15,255,255,255,255,255,255,255,255,193,0,15,255,255,253,116,35,109,255,255,248,
0,15,255,255,144,0,0,1,191,255,254,16,15,255,251,0,0,0,0,29,255,255,112,6,
102,98,0,0,0,0,7,255,255,176,0,0,0,0,0,0,0,2,255,255,208,0,0,0,
0,0,0,0,1,255,255,240,34,34,32,0,0,0,0,1,255,255,224,239,255,243,0,0,
0,0,2,255,255,208,223,255,247,0,0,0,0,7,255,255,160,159,255,254,16,0,0,0,
30,255,255,96,63,255,255,193,0,0,1,191,255,254,16,10,255,255,254,132,35,125,255,255,
247,0,1,207,255,255,255,255,255,255,255,176,0,0,27,255,255,255,255,255,255,250,16,0,
0,0,110,255,255,255,255,254,96,0,0,0,0,1,106,222,254,218,97,0,0,0,0,0,
0,22,189,254,220,115,0,0,0,0,0,6,239,255,255,255,255,162,0,0,0,0,175,255,
255,255,255,255,254,64,0,0,9,255,255,255,255,255,255,255,227,0,0,111,255,255,181,34,
73,255,255,251,0,1,223,255,246,0,0,0,78,255,255,64,7,255,255,128,0,0,0,8,
255,255,128,12,255,254,16,0,0,0,1,68,68,32,47,255,249,0,0,0,0,0,0,0,
0,111,255,245,0,90,223,237,147,0,0,0,143,255,242,60,255,255,255,255,161,0,0,191,
255,244,239,255,255,255,255,253,32,0,223,255,253,255,255,255,255,255,255,209,0,239,255,255,
255,197,35,109,255,255,249,0,239,255,255,248,0,0,1,191,255,255,32,255,255,255,176,0,
0,0,30,255,255,112,239,255,255,48,0,0,0,7,255,255,176,223,255,253,0,0,0,0,
2,255,255,224,207,255,251,0,0,0,0,1,255,255,240,159,255,251,0,0,0,0,1,255,
255,224,111,255,253,0,0,0,0,3,255,255,208,47,255,255,48,0,0,0,7,255,255,176,
12,255,255,176,0,0,0,30,255,255,96,5,255,255,248,0,0,1,207,255,254,16,0,191,
255,255,181,35,125,255,255,246,0,0,46,255,255,255,255,255,255,255,160,0,0,3,239,255,
255,255,255,255,250,16,0,0,0,26,255,255,255,255,254,96,0,0,0,0,0,55,205,255,
218,97,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,34,34,34,
34,34,34,34,143,255,252,0,0,0,0,0,0,1,239,255,244,0,0,0,0,0,0,8,
255,255,192,0,0,0,0,0,0,30,255,255,64,0,0,0,0,0,0,143,255,252,0,0,
0,0,0,0,1,239,255,244,0,0,0,0,0,0,8,255,255,176,0,0,0,0,0,0,
30,255,255,64,0,0,0,0,0,0,143,255,251,0,0,0,0,0,0,1,239,255,244,0,
0,0,0,0,0,9,255,255,176,0,0,0,0,0,0,47,255,255,64,0,0,0,0,0,
0,159,255,251,0,0,0,0,0,0,2,255,255,243,0,0,0,0,0,0,9,255,255,176,
0,0,0,0,0,0,47,255,255,48,0,0,0,0,0,0,159,255,251,0,0,0,0,0,
0,2,255,255,243,0,0,0,0,0,0,10,255,255,160,0,0,0,0,0,0,47,255,255,
48,0,0,0,0,0,0,175,255,250,0,0,0,0,0,0,3,255,255,243,0,0,0,0,
0,0,10,255,255,160,0,0,0,0,0,0,63,255,255,48,0,0,0,0,0,0,191,255,
250,0,0,0,0,0,0,0,0,1,90,206,254,202,81,0,0,0,0,0,110,255,255,255,
255,254,96,0,0,0,10,255,255,255,255,255,255,251,0,0,0,175,255,255,255,255,255,255,
255,160,0,4,255,255,254,115,35,126,255,255,245,0,10,255,255,209,0,0,1,223,255,251,
0,14,255,255,80,0,0,0,95,255,254,0,15,255,255,16,0,0,0,31,255,255,0,14,
255,255,16,0,0,0,31,255,254,0,10,255,255,80,0,0,0,95,255,250,0,4,255,255,
209,0,0,2,223,255,244,0,0,143,255,254,115,35,126,255,255,128,0,0,7,239,255,255,
255,255,255,230,0,0,0,0,23,223,255,255,255,215,16,0,0,0,2,174,255,255,255,255,
254,162,0,0,0,111,255,255,255,255,255,255,255,96,0,6,255,255,251,81,1,91,255,255,
246,0,47,255,255,96,0,0,0,111,255,255,32,143,255,248,0,0,0,0,8,255,255,128,
207,255,242,0,0,0,0,2,255,255,192,239,255,240,0,0,0,0,0,255,255,224,239,255,
242,0,0,0,0,2,255,255,224,207,255,248,0,0,0,0,8,255,255,192,143,255,255,80,
0,0,0,95,255,255,128,46,255,255,251,99,35,107,255,255,254,32,6,255,255,255,255,255,
255,255,255,245,0,0,111,255,255,255,255,255,255,255,96,0,0,3,191,255,255,255,255,255,
179,0,0,0,0,2,123,222,254,219,114,0,0,0,0,0,0,89,206,254,218,97,0,0,
0,0,0,110,255,255,255,255,254,112,0,0,0,27,255,255,255,255,255,255,252,16,0,0,
207,255,255,255,255,255,255,255,209,0,9,255,255,254,132,35,108,255,255,250,0,47,255,255,
194,0,0,0,143,255,255,64,143,255,253,16,0,0,0,10,255,255,176,207,255,246,0,0,
0,0,2,255,255,242,239,255,242,0,0,0,0,0,207,255,246,255,255,240,0,0,0,0,
0,175,255,249,239,255,242,0,0,0,0,0,207,255,252,207,255,246,0,0,0,0,1,255,
255,253,159,255,253,16,0,0,0,10,255,255,254,63,255,255,177,0,0,0,143,255,255,255,
10,255,255,254,132,35,108,255,255,255,254,1,223,255,255,255,255,255,255,239,255,253,0,45,
255,255,255,255,255,254,95,255,252,0,1,159,255,255,255,255,195,47,255,251,0,0,2,140,
239,237,165,0,79,255,248,0,0,0,0,0,0,0,0,143,255,245,0,0,0,0,0,0,
0,0,207,255,241,156,204,196,0,0,0,0,4,255,255,176,143,255,250,0,0,0,0,28,
255,255,80,63,255,255,112,0,0,1,191,255,252,0,10,255,255,252,83,35,126,255,255,243,
0,1,223,255,255,255,255,255,255,255,96,0,0,29,255,255,255,255,255,255,247,0,0,0,
0,143,255,255,255,255,252,48,0,0,0,0,1,123,223,254,201,64,0,0,0,
};
static const VGlyph F_CODE_glyphs[] = {
{32,0,0,0,0,0,144},
{48,0,23,29,1,-29,416},
{49,348,12,29,1,-29,240},
{50,522,20,29,2,-29,368},
{51,812,22,29,2,-29,400},
{52,1131,24,29,1,-29,416},
{53,1479,21,29,1,-29,384},
{54,1798,21,29,1,-29,384},
{55,2117,20,29,1,-29,352},
{56,2407,21,29,1,-29,384},
{57,2726,22,29,1,-29,384},
};
static const VFont F_CODE = { F_CODE_glyphs, 11, F_CODE_bmp, 39, 10 };
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.