gb: updated presets

This commit is contained in:
Steven Hugg committed 2026-09-30 01:36:26 +02:00
1 parent a5a6ea184b
commit 6bc9ac85f2
16 files changed
+2151 -4

No files matched your search

+2 -1
View File
@@ -12,4 +12,5 @@ config.js
chromedriver.log
nightwatch.conf.js
/src/docs/toolchains.md
LICENSES/
LICENSES/
.vscode
+118
View File
@@ -0,0 +1,118 @@
/*
Streaming animation: only the current frame lives in VRAM.
A 32x32 pre-rendered ball (see ballframes.h, made by the book's
tools/prerender.py) has 16 animation frames of 16 tiles each: 256 tiles,
which is more than the sprite half of VRAM holds for *everything*. Games
with lots of big, smooth animation -- Donkey Kong Land, Shantae -- don't
keep every frame in VRAM. They keep only the frame being shown, and copy
the next one in from ROM as it's needed.
To avoid showing a half-copied frame, sprite tiles are double-buffered:
tiles 0..15 set A the sprites point at one set while we
tiles 16..31 set B copy the next frame into the other, then flip
How fast is the copy? On a DMG the CPU copies 256 bytes with the usual
VRAM-safe loop. On a Game Boy Color, one general-purpose DMA (GDMA)
moves it in about one scanline while the CPU waits. copy_start and
copy_end record the scanline (LY) before and after, so you can compare:
run this on the "gb" and "gb.color" platforms and watch them in the
memory view.
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "gb/cgb.h"
// DMA reads its source on 16-byte boundaries, so pin the frames to an
// aligned address in the upper ROM (this demo's code stays below it)
#define BALL_FRAMES_AT __at(0x7000)
#include "ballframes.h"
#define SET_A 0
#define SET_B 16
uint8_t copy_start, copy_end; // LY before and after the last copy
// Copy one frame's 16 tiles into sprite tiles 'dest'..dest+15.
void load_frame(uint8_t frame, uint8_t dest) {
const uint8_t* src = ball_frames[frame];
copy_start = LY_REG;
if (_cpu == CGB_TYPE) {
uint16_t s = (uint16_t)src;
uint16_t d = 0x8000 + dest * 16;
HDMA1_REG = s >> 8; // source, high/low
HDMA2_REG = s & 0xff;
HDMA3_REG = d >> 8; // destination in VRAM, high/low
HDMA4_REG = d & 0xff;
HDMA5_REG = BALL_TILES - 1; // bit 7 = 0: general-purpose, 16 blocks
// of 16 bytes; the CPU halts until done
} else {
set_sprite_data(dest, BALL_TILES, src);
}
copy_end = LY_REG;
}
// point the 8 sprites (8x16 mode, 4 across x 2 down) at a tile set
void show_set(uint8_t base) {
uint8_t i;
for (i = 0; i < 8; i++) set_sprite_tile(i, base + i * 2);
}
void place(uint8_t x, uint8_t y) {
uint8_t i;
for (i = 0; i < 8; i++) {
move_sprite(i, x + (i & 3) * 8, y + (i >> 2) * 16);
}
}
/*{pal:555,n:4,bpw:16}*/
const palette_color_t ball_pal[4] = {
0x0000, 0x4B9F, 0x11DC, 0x088C // RGB(0,0,0), (31,28,18), (28,14,4), (12,4,2)
};
/*{pal:555,n:4,bpw:16}*/
const palette_color_t bg_pal[4] = {
0x7F54, 0x2A8A, 0x1986, 0x0000 // RGB(20,26,31), (10,20,10), (6,12,6), (0,0,0)
};
void main(void) {
uint8_t frame = 0, tick = 0, cur = SET_A, x = 20;
int8_t dx = 1;
DISPLAY_OFF;
BGP_REG = 0xE4;
// sprite colors 1..3 = white, light gray, dark gray (0 is transparent)
OBP0_REG = 0x90;
if (_cpu == CGB_TYPE) {
set_sprite_palette(0, 1, (palette_color_t*)ball_pal);
set_bkg_palette(0, 1, (palette_color_t*)bg_pal);
}
SPRITES_8x16;
load_frame(0, SET_A);
show_set(SET_A);
place(x, 70);
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
while (1) {
wait_vbl_done();
// next frame every other video frame: 30 fps animation. Copy first,
// while we're still in VBlank: GDMA must not run while the PPU is
// drawing (mode 3), because VRAM writes are ignored then.
if (++tick & 1) {
uint8_t back = (cur == SET_A) ? SET_B : SET_A;
frame = (frame + (dx > 0 ? 1 : BALL_FRAMES - 1)) & (BALL_FRAMES - 1);
load_frame(frame, back); // copy into the hidden set...
show_set(back); // ...then flip (OAM updates next VBlank)
cur = back;
}
// roll back and forth
x += dx;
if (x < 10 || x > 130) dx = -dx;
place(x, 70);
}
}
+208
View File
@@ -0,0 +1,208 @@
/*
Generates random sounds in the Game Boy APU, printing the parameters
to the screen. Also shows an asterisk while each channel is
playing, i.e. while its length counter is active (NR52 bits 0-3).
Port of presets/nes/aputest.c. The Game Boy has two pulse channels
(the first with a frequency sweep), a wave channel and a noise channel.
Only one or two channels are picked at a time so that all of their
parameters fit on the 20x18 tile screen.
A = new sound, B or START = replay the same sound.
*/
#include <stdint.h>
#include <string.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "gbtext.h"
typedef uint8_t byte;
typedef uint16_t word;
// channel bits, same as NR52 status bits
#define CH_PULSE1 0x01
#define CH_PULSE2 0x02
#define CH_WAVE 0x04
#define CH_NOISE 0x08
typedef struct APUParam {
byte chmask;
const char* name;
word valmask;
word valmin;
} APUParam;
// parameter indices (order of APU_DEFS)
enum {
P1_FREQ, P1_DUTY, P1_VOL, P1_ENVPER, P1_LEN, P1_SWPPER, P1_SWPDN, P1_SWPSHF,
P2_FREQ, P2_DUTY, P2_VOL, P2_ENVPER, P2_LEN,
W_FREQ, W_LEVEL, W_LEN,
N_SHIFT, N_WIDTH, N_DIV, N_VOL, N_ENVPER, N_LEN,
APU_DEFCOUNT
};
const APUParam APU_DEFS[APU_DEFCOUNT] = {
{CH_PULSE1, "Pulse1 Freq", 0x7ff, 0x100 },
{CH_PULSE1, "Pulse1 Duty", 0x03, 0 },
{CH_PULSE1, "Pulse1 Vol", 0x0f, 1 },
{CH_PULSE1, "Pulse1 EnvPer", 0x07, 0 },
{CH_PULSE1, "Pulse1 Length", 0x3f, 0 },
{CH_PULSE1, "Pulse1 SwpPer", 0x07, 0 },
{CH_PULSE1, "Pulse1 SwpDown?",0x01, 0 },
{CH_PULSE1, "Pulse1 SwpShf", 0x07, 0 },
{CH_PULSE2, "Pulse2 Freq", 0x7ff, 0x100 },
{CH_PULSE2, "Pulse2 Duty", 0x03, 0 },
{CH_PULSE2, "Pulse2 Vol", 0x0f, 1 },
{CH_PULSE2, "Pulse2 EnvPer", 0x07, 0 },
{CH_PULSE2, "Pulse2 Length", 0x3f, 0 },
{CH_WAVE, "Wave Freq", 0x7ff, 0x100 },
{CH_WAVE, "Wave Level", 0x03, 1 },
{CH_WAVE, "Wave Length", 0xff, 0 },
{CH_NOISE, "Noise Shift", 0x0f, 0 },
{CH_NOISE, "Noise 7-bit?", 0x01, 0 },
{CH_NOISE, "Noise Divisor", 0x07, 0 },
{CH_NOISE, "Noise Vol", 0x0f, 1 },
{CH_NOISE, "Noise EnvPer", 0x07, 0 },
{CH_NOISE, "Noise Length", 0x3f, 0 },
};
/* 32-sample waveform, two 4-bit samples per byte. */
const byte wave_pattern[16] = {
0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10
};
// 16-bit LCG
static word rnd = 0xCACE;
static word rand16(void) {
rnd = rnd * 17 + 53;
return rnd ^ (rnd >> 7);
}
byte enmask;
word vals[APU_DEFCOUNT];
static byte popcount4(byte x) {
return (x & 1) + ((x >> 1) & 1) + ((x >> 2) & 1) + ((x >> 3) & 1);
}
void random_sound(void) {
byte i;
// pick one or two channels
do {
enmask = rand16() & 15;
} while (enmask == 0 || popcount4(enmask) > 2);
for (i = 0; i < APU_DEFCOUNT; i++) {
const APUParam* p = &APU_DEFS[i];
vals[i] = p->valmin + (rand16() & p->valmask);
if (vals[i] > p->valmask && p->valmask < 0x100) vals[i] = p->valmask;
if (vals[i] > 0x7ff) vals[i] = 0x7ff;
}
}
// write decimal number, right-aligned in a field of 'width' chars
static void put_num(byte x, byte y, word v, byte width) {
char buf[6];
byte i = width;
memset(buf, ' ', width);
do {
buf[--i] = '0' + (v % 10);
v /= 10;
} while (v && i);
set_bkg_tiles(x, y, width, 1, (uint8_t*)buf);
}
// only rows of enabled channels are shown, in a compact list;
// row_ch[] remembers which channel each screen row belongs to
byte row_ch[16];
byte row_count;
void print_sound(void) {
byte i;
byte y = 0;
// clear the parameter area
for (i = 0; i < 16; i++) put_str(0, i, " ");
row_count = 0;
for (i = 0; i < APU_DEFCOUNT; i++) {
if ((enmask & APU_DEFS[i].chmask) && y < 16) {
put_str(0, y, APU_DEFS[i].name);
put_num(14, y, vals[i], 5);
row_ch[y] = APU_DEFS[i].chmask;
y++;
}
}
row_count = y;
}
void play_sound(void) {
byte envdown = 0; // envelope always decays (bit 3 = 0)
NR51_REG = enmask | (enmask << 4); // route only the chosen channels
if (enmask & CH_PULSE1) {
NR10_REG = (vals[P1_SWPPER] << 4) | (vals[P1_SWPDN] << 3) | vals[P1_SWPSHF];
NR11_REG = (vals[P1_DUTY] << 6) | vals[P1_LEN];
NR12_REG = (vals[P1_VOL] << 4) | envdown | vals[P1_ENVPER];
NR13_REG = vals[P1_FREQ] & 0xff;
NR14_REG = 0xc0 | (vals[P1_FREQ] >> 8); // trigger + length enable
}
if (enmask & CH_PULSE2) {
NR21_REG = (vals[P2_DUTY] << 6) | vals[P2_LEN];
NR22_REG = (vals[P2_VOL] << 4) | envdown | vals[P2_ENVPER];
NR23_REG = vals[P2_FREQ] & 0xff;
NR24_REG = 0xc0 | (vals[P2_FREQ] >> 8);
}
if (enmask & CH_WAVE) {
NR30_REG = 0x80; // wave DAC on
NR31_REG = vals[W_LEN];
NR32_REG = vals[W_LEVEL] << 5;
NR33_REG = vals[W_FREQ] & 0xff;
NR34_REG = 0xc0 | (vals[W_FREQ] >> 8);
}
if (enmask & CH_NOISE) {
NR41_REG = vals[N_LEN];
NR42_REG = (vals[N_VOL] << 4) | envdown | vals[N_ENVPER];
NR43_REG = (vals[N_SHIFT] << 4) | (vals[N_WIDTH] << 3) | vals[N_DIV];
NR44_REG = 0xc0;
}
}
void print_status(void) {
byte i;
byte st = NR52_REG;
for (i = 0; i < row_count; i++) {
put_char(19, i, (st & row_ch[i]) ? '*' : ' ');
}
}
void apu_init(void) {
byte i;
NR52_REG = 0x80; // sound on
NR50_REG = 0x77; // master volume
NR30_REG = 0x00; // wave DAC off while loading wave RAM
for (i = 0; i < 16; i++) _AUD3WAVERAM[i] = wave_pattern[i];
}
void main(void) {
byte keys;
DISPLAY_OFF;
BGP_REG = 0xE4;
font_init();
put_str(0, 17, "A=New B/Start=Replay");
SHOW_BKG;
DISPLAY_ON;
apu_init();
random_sound();
while (1) {
print_sound();
play_sound();
// wait for a key, updating the channel status each frame
do {
wait_vbl_done();
print_status();
rnd++; // stir the RNG while waiting
keys = joypad();
while (joypad()) ;
} while (!(keys & (J_A | J_B | J_START)));
if (keys & J_A) random_sound();
}
}
+310
View File
@@ -0,0 +1,310 @@
/*
ballframes.h -- generated by tools/prerender.py; do not edit.
16 frames of a 32x32 lit, striped sphere, each frame
16 tiles in 8x16 sprite order (4 sprites across, 2 down).
raw: 256 tiles = 4096 bytes
unique: 188 tiles = 3008 bytes (no blank tile)
*/
#define BALL_FRAMES 16
#define BALL_TILES 16
// define BALL_FRAMES_AT (e.g. __at(0x7000)) before including to pin
// the data to an address -- DMA needs a 16-byte-aligned source
#ifndef BALL_FRAMES_AT
#define BALL_FRAMES_AT
#endif
/*{w:32,h:32,bpp:1,count:16,brev:1,np:2,pofs:1,sl:4,wpimg:256,remap:[5,6,1,2,3,4,7,0,8,9,10,11]}*/
const uint8_t BALL_FRAMES_AT ball_frames[BALL_FRAMES][BALL_TILES * 16] = {
{ // frame 0
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x01,0x06,0x03,0x08,0x07,0x10,0x0F,
0x30,0x0F,0x12,0x2F,0x20,0x1F,0x48,0x3F,0x60,0x1F,0x42,0x3F,0x20,0x5F,0x4A,0x3F,
0x00,0x00,0x02,0x1F,0x80,0x7F,0x8A,0xFF,0x00,0xFF,0x22,0xFF,0x00,0xFF,0x80,0xFF,
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
0x00,0x00,0x40,0xB8,0xAB,0x55,0x50,0xAF,0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x40,0x20,0xE0,0x10,0xF0,0x08,0xF8,
0x84,0x7C,0x2C,0xFC,0x04,0xFC,0x2E,0xFE,0x04,0xFE,0x2E,0xFE,0x04,0xFE,0xAE,0xFE,
0x20,0x5F,0x4A,0x3F,0x24,0x5F,0x0A,0x7F,0x31,0x5F,0x0A,0x3F,0x05,0x3F,0x2A,0x3F,
0x05,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x15,0x1F,0x00,0x00,
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAE,0xFE,0x54,0xFE,0xAE,0xFE,0x54,0xFE,0xAC,0xFC,0x54,0xFC,0xAC,0xFC,
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 1
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x03,0x02,0x07,0x08,0x07,0x18,0x0F,
0x20,0x1F,0x02,0x3F,0x20,0x1F,0x68,0x3F,0x40,0x3F,0x62,0x3F,0x00,0x7F,0x6A,0x3F,
0x00,0x00,0x03,0x1E,0x81,0x7E,0x8B,0xFE,0x01,0xFE,0x23,0xFE,0x03,0xFC,0x81,0xFE,
0x0B,0xF4,0x17,0xE8,0x3B,0xC4,0x13,0xEC,0x2A,0xD5,0x01,0xFE,0x02,0xFD,0xA9,0xFE,
0x00,0x00,0x48,0xB8,0xA9,0x57,0x50,0xAF,0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0x20,0xE0,0x10,0xF0,0x28,0xF8,
0x94,0x7C,0x3C,0xFC,0x14,0xFC,0x2E,0xFE,0x04,0xFE,0x2E,0xFE,0x04,0xFE,0xAE,0xFE,
0x00,0x7F,0x6A,0x3F,0x04,0x7F,0x2A,0x7F,0x11,0x7F,0x0A,0x3F,0x15,0x3F,0x2A,0x3F,
0x05,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0xFD,0xA8,0xFF,0x46,0xFD,0xA8,0xFF,0x10,0xFF,0xA8,0xFF,0x54,0xFF,0xAC,0xFF,
0x54,0xFF,0xBA,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFE,0xFF,0x15,0x1F,0x00,0x00,
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAE,0xFE,0x54,0xFE,0xAE,0xFE,0x54,0xFE,0xBC,0xFC,0x54,0xFC,0xBC,0xFC,
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 2
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
0x20,0x1F,0x22,0x3F,0x00,0x3F,0x68,0x3F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
0x00,0x00,0x03,0x1E,0x83,0x7C,0x89,0xFE,0x07,0xF8,0x25,0xFA,0x07,0xF8,0x85,0xFA,
0x0F,0xF0,0x1F,0xE0,0x3F,0xC0,0x1F,0xE0,0x2E,0xD1,0x0D,0xF2,0x0A,0xF5,0xA5,0xFA,
0x00,0x00,0x48,0xB8,0xAD,0x57,0x52,0xAF,0xEB,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0x28,0xF8,
0x94,0x7C,0x3C,0xFC,0x14,0xFC,0x2E,0xFE,0x14,0xFE,0x3E,0xFE,0x14,0xFE,0xBE,0xFE,
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x11,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x0A,0xF5,0xA4,0xFB,0x4A,0xF5,0xA0,0xFF,0x18,0xF7,0xA0,0xFF,0x50,0xFF,0xA0,0xFF,
0x50,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFE,0xFF,0x15,0x1F,0x00,0x00,
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xBE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xBC,0xFC,0x54,0xFC,0xBC,0xFC,
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 3
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
0x20,0x1F,0x22,0x3F,0x00,0x3F,0x28,0x7F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
0x00,0x00,0x01,0x1E,0x07,0xF8,0x85,0xFA,0x0F,0xF0,0x2D,0xF2,0x1F,0xE0,0x95,0xEA,
0x1F,0xE0,0x1F,0xE0,0x3F,0xC0,0x3F,0xC0,0x3E,0xC1,0x3D,0xC2,0x3A,0xC5,0x95,0xEA,
0x00,0x00,0x48,0xB8,0xAD,0x57,0x52,0xAF,0xE9,0x17,0x54,0xAB,0xAB,0x55,0x50,0xAF,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
0x54,0xFC,0x3C,0xFC,0x54,0xFC,0x6E,0xFE,0x54,0xFE,0x3E,0xFE,0x54,0xFE,0xFE,0xFE,
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x51,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x2A,0xD5,0x94,0xEB,0x6A,0xD5,0x80,0xFF,0x28,0xD7,0x80,0xFF,0x40,0xFF,0xA0,0xFF,
0x40,0xFF,0xA2,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x15,0x1F,0x00,0x00,
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xFC,0xFC,
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 4
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
0x00,0x3F,0x22,0x3F,0x00,0x3F,0x28,0x7F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
0x00,0x00,0x05,0x1A,0x0F,0xF0,0x95,0xEA,0x1F,0xE0,0x1D,0xE2,0x3F,0xC0,0xB5,0xCA,
0x7F,0x80,0x7F,0x80,0x7F,0x80,0x7F,0x80,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
0x00,0x00,0x48,0xB8,0xA5,0x5F,0x52,0xAF,0xE9,0x17,0x56,0xAB,0xA9,0x57,0x52,0xAF,
0xEB,0x15,0x54,0xAB,0xAB,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x51,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0x28,0xD7,0x80,0xFF,0x20,0xDF,0x80,0xFF,
0x40,0xFF,0xA2,0xFF,0x45,0xFF,0xEA,0xFF,0x55,0xFF,0xFA,0xFF,0x15,0x1F,0x00,0x00,
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2B,0xFF,0x05,0xFF,0xAB,0xFF,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xFC,0xFC,
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 5
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
0x00,0x3F,0x23,0x3E,0x01,0x3E,0x29,0x7E,0x01,0x7E,0x23,0x7E,0x01,0x7E,0x2B,0x7E,
0x00,0x00,0x05,0x1A,0x1F,0xE0,0x95,0xEA,0x3F,0xC0,0x5D,0xA2,0x7F,0x80,0x75,0x8A,
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
0x00,0x00,0x68,0xB8,0xA5,0x5F,0x5A,0xAF,0xE1,0x1F,0x5A,0xAF,0xAD,0x57,0x52,0xAF,
0xE9,0x17,0x52,0xAF,0xA9,0x57,0x52,0xAF,0xA9,0x57,0x42,0xBF,0xA1,0x5F,0x02,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
0x00,0x7F,0x2B,0x7E,0x44,0x7F,0x2B,0x7E,0x50,0x7F,0x2A,0x3F,0x14,0x3F,0x2A,0x3F,
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
0x00,0xFF,0xA2,0xFF,0x05,0xFF,0xEA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
0xA9,0x57,0x03,0xFF,0x01,0xFF,0x0B,0xFF,0x81,0x7F,0x2B,0xFF,0x05,0xFF,0xAF,0xFF,
0x15,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBF,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xF8,0xFC,
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 6
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x01,0x0E,0x09,0x1E,
0x03,0x3C,0x21,0x3E,0x03,0x3C,0x29,0x7E,0x07,0x78,0x25,0x7A,0x03,0x7C,0x2D,0x7A,
0x00,0x00,0x05,0x1A,0x1F,0xE0,0x95,0xEA,0x7F,0x80,0xDD,0x22,0xFF,0x00,0x75,0x8A,
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
0x00,0x00,0x28,0xF8,0x84,0x7F,0x6A,0xBF,0xE1,0x1F,0x4A,0xBF,0xA5,0x5F,0x4A,0xBF,
0xE1,0x1F,0x5A,0xAF,0xA5,0x5F,0x5A,0xAF,0xA1,0x5F,0x4A,0xBF,0xA5,0x5F,0x0A,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
0x06,0x79,0x2D,0x7A,0x42,0x7D,0x29,0x7E,0x52,0x7D,0x28,0x3F,0x16,0x3D,0x28,0x3F,
0x14,0x1F,0x0A,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
0xA5,0x5F,0x0B,0xFF,0x05,0xFF,0x0F,0xFF,0x85,0x7F,0x2B,0xFF,0x05,0xFF,0xAF,0xFF,
0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xBF,0xFF,0x55,0xFF,0xBE,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xF8,0xFC,
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 7
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x03,0x06,0x03,0x0C,0x0B,0x1C,
0x07,0x38,0x25,0x3A,0x07,0x38,0x25,0x7A,0x0F,0x70,0x25,0x7A,0x0B,0x74,0x25,0x7A,
0x00,0x00,0x05,0x1A,0x3F,0xC0,0xD5,0xAA,0xFF,0x00,0xDD,0x22,0xFF,0x00,0x75,0x8A,
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
0x00,0x00,0x28,0xF8,0x84,0x7F,0x2A,0xFF,0x81,0x7F,0x2A,0xFF,0x85,0x7F,0x6A,0xBF,
0xC1,0x3F,0x6A,0xBF,0x85,0x7F,0x6A,0xBF,0x91,0x7F,0x6A,0xBF,0x85,0x7F,0x2A,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0xC0,0xA0,0xE0,0x40,0xF0,0xA8,0xF8,
0x54,0xFC,0xB8,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
0x0E,0x71,0x25,0x7A,0x4A,0x75,0x21,0x7E,0x5A,0x75,0x28,0x3F,0x12,0x3D,0x28,0x3F,
0x14,0x1F,0x0A,0x0F,0x04,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
0x95,0x7F,0x2B,0xFF,0x15,0xFF,0x2F,0xFF,0x95,0x7F,0x3B,0xFF,0x15,0xFF,0xBF,0xFF,
0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xF8,0xFC,0x54,0xFC,0xF8,0xFC,
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 8
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x01,0x06,0x07,0x08,0x0F,0x18,
0x0F,0x30,0x2D,0x32,0x1F,0x20,0x35,0x6A,0x1F,0x60,0x35,0x6A,0x1B,0x64,0x35,0x6A,
0x00,0x00,0x15,0x0A,0x7F,0x80,0x55,0xAA,0xFF,0x00,0xDD,0x22,0xFF,0x00,0x75,0x8A,
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
0x00,0x00,0xA8,0xF8,0x04,0xFF,0xAA,0xFF,0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0xC0,0xA0,0xE0,0x40,0xF0,0xA8,0xF8,
0x54,0xFC,0xB8,0xFC,0x54,0xFC,0xEA,0xFE,0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,
0x0E,0x71,0x35,0x6A,0x4A,0x75,0x21,0x7E,0x4A,0x75,0x20,0x3F,0x12,0x3D,0x20,0x3F,
0x10,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x15,0x1F,0x00,0x00,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,0x54,0xFE,0xF8,0xFC,0x54,0xFC,0xF8,0xFC,
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 9
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x00,0x05,0x02,0x07,0x08,0x07,0x18,
0x1F,0x20,0x3D,0x22,0x1F,0x20,0x15,0x6A,0x3F,0x40,0x15,0x6A,0x3B,0x44,0x15,0x6A,
0x00,0x00,0x14,0x0B,0x7E,0x81,0x54,0xAB,0xFE,0x01,0xDC,0x23,0xFC,0x03,0x74,0x8B,
0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFE,0x03,0xF8,0x07,0x56,0xAB,
0x00,0x00,0xA0,0xF8,0x06,0xFD,0xAA,0xFF,0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xA0,0xE0,0x40,0xF0,0x88,0xF8,
0x44,0xFC,0xA8,0xFC,0x44,0xFC,0xEA,0xFE,0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,
0x2E,0x51,0x15,0x6A,0x6A,0x55,0x01,0x7E,0x6A,0x55,0x20,0x3F,0x02,0x3D,0x20,0x3F,
0x10,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xE9,0x17,0x56,0xAB,0xA9,0x57,0x02,0xFF,0xA9,0x57,0x02,0xFF,0x21,0xDF,0x02,0xFF,
0x01,0xFF,0x23,0xFF,0x05,0xFF,0xAB,0xFF,0x55,0xFF,0xAB,0xFF,0x15,0x1F,0x00,0x00,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,0x54,0xFE,0xE8,0xFC,0x54,0xFC,0xE8,0xFC,
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 10
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
0x1F,0x20,0x1D,0x22,0x3F,0x00,0x15,0x6A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
0x00,0x00,0x14,0x0B,0x7C,0x83,0x56,0xAB,0xF8,0x07,0xDA,0x27,0xF8,0x07,0x70,0x8F,
0xF8,0x07,0xF4,0x0B,0xF8,0x07,0xF0,0x0F,0xF8,0x07,0xF2,0x0F,0xF0,0x0F,0x5A,0xAF,
0x00,0x00,0xA0,0xF8,0x02,0xFD,0xA8,0xFF,0x00,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x88,0xF8,
0x44,0xFC,0xA8,0xFC,0x44,0xFC,0xEA,0xFE,0x44,0xFE,0xAA,0xFE,0x44,0xFE,0xEA,0xFE,
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x6A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xE1,0x1F,0x5A,0xAF,0xA5,0x5F,0x0A,0xFF,0xA1,0x5F,0x0A,0xFF,0x25,0xDF,0x0E,0xFF,
0x05,0xFF,0x23,0xFF,0x05,0xFF,0xAF,0xFF,0x55,0xFF,0xAB,0xFF,0x15,0x1F,0x00,0x00,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xEA,0xFE,0x54,0xFE,0xE8,0xFC,0x54,0xFC,0xE8,0xFC,
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 11
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
0x1F,0x20,0x1D,0x22,0x3F,0x00,0x55,0x2A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
0x00,0x00,0x16,0x0B,0xF8,0x07,0x5A,0xAF,0xF0,0x0F,0xD2,0x2F,0xE0,0x1F,0x60,0x9F,
0xE8,0x17,0xF4,0x0B,0xF8,0x07,0xD0,0x2F,0xE8,0x17,0xC2,0x3F,0xC0,0x3F,0x6A,0xBF,
0x00,0x00,0xA0,0xF8,0x02,0xFD,0xA8,0xFF,0x02,0xFD,0x2A,0xFF,0x04,0xFF,0xAA,0xFF,
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
0x84,0x7C,0xA8,0xFC,0x04,0xFC,0xAA,0xFE,0x04,0xFE,0xAA,0xFE,0x04,0xFE,0xAA,0xFE,
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x2A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0xC1,0x3F,0x6A,0xBF,0x85,0x7F,0x2A,0xFF,0x91,0x7F,0x2A,0xFF,0x35,0xDF,0x0E,0xFF,
0x15,0xFF,0x3B,0xFF,0x05,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x15,0x1F,0x00,0x00,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xA8,0xFC,
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 12
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
0x3F,0x00,0x1D,0x22,0x3F,0x00,0x55,0x2A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
0x00,0x00,0x12,0x0F,0xF0,0x0F,0x4A,0xBF,0xE0,0x1F,0xE2,0x3F,0xC0,0x3F,0x40,0xBF,
0x88,0x77,0x94,0x6B,0xB8,0x47,0x90,0x6F,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
0x00,0x00,0xA0,0xF8,0x0A,0xF5,0xA8,0xFF,0x02,0xFD,0x28,0xFF,0x06,0xFD,0xA8,0xFF,
0x00,0xFF,0x2A,0xFF,0x04,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x2A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x91,0x7F,0x2A,0xFF,0x55,0xFF,0x2E,0xFF,
0x15,0xFF,0x3B,0xFF,0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xAF,0xFF,0x15,0x1F,0x00,0x00,
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBA,0xFF,0x55,0xFF,0xFE,0xFF,
0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xA8,0xFC,
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 13
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
0x3F,0x00,0x1C,0x23,0x3E,0x01,0x54,0x2B,0x7E,0x01,0x54,0x2B,0x3A,0x45,0x54,0x2B,
0x00,0x00,0x12,0x0F,0xE0,0x1F,0x4A,0xBF,0xC0,0x3F,0xA2,0x7F,0x80,0x7F,0x80,0xFF,
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
0x00,0x00,0x80,0xF8,0x0A,0xF5,0xA0,0xFF,0x0A,0xF5,0x24,0xFB,0x02,0xFD,0xA8,0xFF,
0x02,0xFD,0x2C,0xFB,0x06,0xFD,0xA8,0xFF,0x10,0xFF,0xA8,0xFF,0x44,0xFF,0xA8,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
0x2E,0x51,0x54,0x2B,0x2A,0x55,0x00,0x7F,0x2B,0x55,0x00,0x3F,0x03,0x3D,0x20,0x3F,
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
0x55,0xFF,0x3B,0xFF,0x55,0xFF,0xBF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
0x14,0xFF,0xAA,0xFF,0x54,0xFF,0xAE,0xFF,0x55,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,
0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xEA,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xAC,0xFC,
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 14
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0E,0x01,0x16,0x09,
0x3C,0x03,0x1E,0x23,0x3C,0x03,0x54,0x2B,0x78,0x07,0x52,0x2F,0x38,0x47,0x52,0x2F,
0x00,0x00,0x12,0x0F,0xE0,0x1F,0x4A,0xBF,0x80,0x7F,0x22,0xFF,0x00,0xFF,0x80,0xFF,
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
0x00,0x00,0xC0,0xB8,0x2B,0xD5,0x90,0xEF,0x0A,0xF5,0x34,0xEB,0x0A,0xF5,0xB0,0xEF,
0x0A,0xF5,0x24,0xFB,0x0A,0xF5,0xA0,0xFF,0x18,0xF7,0xA0,0xFF,0x40,0xFF,0xA0,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
0x28,0x57,0x52,0x2F,0x2C,0x57,0x02,0x7F,0x29,0x57,0x02,0x3F,0x01,0x3F,0x22,0x3F,
0x01,0x1F,0x0B,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
0x18,0xF7,0xA2,0xFF,0x50,0xFF,0xAA,0xFF,0x51,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,
0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xEB,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xAC,0xFC,
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
{ // frame 15
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x04,0x03,0x0C,0x03,0x14,0x0B,
0x38,0x07,0x1A,0x27,0x38,0x07,0x58,0x2F,0x70,0x0F,0x52,0x2F,0x30,0x4F,0x5A,0x2F,
0x00,0x00,0x12,0x0F,0xC0,0x3F,0x0A,0xFF,0x00,0xFF,0x22,0xFF,0x00,0xFF,0x80,0xFF,
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
0x00,0x00,0xC0,0xB8,0x2B,0xD5,0xD0,0xAF,0x6A,0x95,0x54,0xAB,0x2A,0xD5,0x90,0xEF,
0x2A,0xD5,0x14,0xEB,0x2A,0xD5,0x90,0xEF,0x28,0xD7,0x80,0xFF,0x60,0xDF,0x80,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x40,0x20,0xE0,0x10,0xF0,0x08,0xF8,
0x84,0x7C,0x2C,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
0x20,0x5F,0x5A,0x2F,0x24,0x5F,0x0A,0x7F,0x21,0x5F,0x02,0x3F,0x05,0x3F,0x22,0x3F,
0x01,0x1F,0x0B,0x0F,0x05,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
0x28,0xD7,0x82,0xFF,0x40,0xFF,0x8A,0xFF,0x41,0xFF,0xAA,0xFF,0x45,0xFF,0xEA,0xFF,
0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xAC,0xFC,0x54,0xFC,0xAC,0xFC,
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
},
};
+91
View File
@@ -0,0 +1,91 @@
/*
Which Game Boy is this?
The boot ROM leaves a fingerprint in the CPU registers when it jumps to
the cartridge at $0100:
A = $01 original Game Boy (DMG) or Super Game Boy
A = $FF Game Boy Pocket / Light, or Super Game Boy 2
A = $11 Game Boy Color -- or Game Boy Advance, in which case
bit 0 of B is also set
GBDK's startup code (crt0) saves A in _cpu and, on color hardware, the B
bit in _is_GBA, before anything else can overwrite them. The Super Game
Boy can't be told apart by registers; the game has to send it a command
packet and see whether it answers (sgb_check()).
What games did with this:
- Shantae (2002) brightens its palettes on a GBA, whose unlit screen made
GBC colors look muddy, and unlocks a bonus transformation there.
- Many DMG games (Donkey Kong Land, Kirby's Dream Land 2) add a border
and colors when they find a Super Game Boy.
- "Dual-mode" cartridges use the color palettes only if _cpu == CGB_TYPE.
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "gb/cgb.h"
#include "gb/sgb.h"
#include "gbtext.h"
// a normal palette, and a brighter one for the GBA's darker screen.
// Stored as 15-bit CGB words; the RGB() values are in the comments.
/*{pal:555,n:4,bpw:16}*/
const palette_color_t pal_gbc[4] = {
0x639C, 0x2214, 0x110C, 0x0842 // RGB(28,28,24), (20,16,8), (12,8,4), (2,2,2)
};
/*{pal:555,n:4,bpw:16}*/
const palette_color_t pal_gba[4] = {
0x73FF, 0x3B1C, 0x21D4, 0x1084 // RGB(31,31,28), (28,24,14), (20,14,8), (4,4,4)
};
void put_hex(uint8_t x, uint8_t y, uint8_t v) {
char s[3];
const char* hex = "0123456789ABCDEF";
s[0] = hex[v >> 4];
s[1] = hex[v & 15];
s[2] = 0;
put_str(x, y, s);
}
void main(void) {
uint8_t is_sgb;
DISPLAY_OFF;
BGP_REG = 0xE4;
font_init();
fill_bkg_rect(0, 0, 20, 18, ' ');
SHOW_BKG;
DISPLAY_ON;
// the SGB needs a few frames after power-on before it listens
wait_vbl_done(); wait_vbl_done(); wait_vbl_done(); wait_vbl_done();
is_sgb = sgb_check();
put_str(2, 1, "WHICH GAME BOY?");
put_str(2, 4, "_CPU = $");
put_hex(13, 4, _cpu);
put_str(2, 5, "_IS_GBA = $");
put_hex(13, 5, _is_GBA);
put_str(2, 6, "SGB = ");
put_str(12, 6, is_sgb ? "YES" : "NO");
put_str(2, 9, "RUNNING ON:");
if (_cpu == CGB_TYPE && _is_GBA == GBA_DETECTED) {
put_str(3, 11, "GAME BOY ADVANCE");
set_bkg_palette(0, 1, (palette_color_t*)pal_gba);
put_str(3, 12, "(BRIGHT PALETTE)");
} else if (_cpu == CGB_TYPE) {
put_str(3, 11, "GAME BOY COLOR");
set_bkg_palette(0, 1, (palette_color_t*)pal_gbc);
} else if (is_sgb) {
put_str(3, 11, "SUPER GAME BOY");
} else if (_cpu == MGB_TYPE) {
put_str(3, 11, "GAME BOY POCKET");
} else {
put_str(3, 11, "GAME BOY (DMG)");
}
while (1) wait_vbl_done();
}
+100
View File
@@ -0,0 +1,100 @@
/*
Flicker, on purpose: transparency and sprite cycling.
1. Transparency. The Game Boy has no blending hardware, but the original
DMG screen is slow: a pixel takes several frames to fully change. So a
sprite shown on every *other* frame (30 Hz) doesn't blink -- it
smears into a half-tone ghost over the background. Chikyu Kaiho Gun
ZAS (T&E Soft, 1992) used this for see-through clouds and effects,
and Link's Awakening DX (1998) for see-through portraits. On a fast
modern screen or an emulator without frame blending, it just flickers.
2. Sprite cycling. The PPU draws at most 10 sprites per scanline: the
first 10 in OAM order whose rows overlap it. Here 16 balls share one
row, so 6 would simply vanish. Rotating which ball goes in which OAM
slot every frame spreads the loss around: every ball is visible
10 frames out of 16, and the eye sees them all, flickering.
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
/*{w:8,h:8,bpp:1,count:5,brev:1,np:2,pofs:1,sl:2}*/
const uint8_t sprite_tiles[] = {
// 0: ball
0x3C,0x3C, 0x42,0x7E, 0x81,0xFF, 0x85,0xFB, 0x85,0xFB, 0x81,0xFF, 0x42,0x7E, 0x3C,0x3C,
// 1-4: 16-by-16 ghost, solid color 3 (the flicker makes it look gray)
0x07,0x07, 0x1F,0x1F, 0x3F,0x3F, 0x7F,0x7F, 0x73,0x73, 0xF3,0xF3, 0xFF,0xFF, 0xFF,0xFF,
0xE0,0xE0, 0xF8,0xF8, 0xFC,0xFC, 0xFE,0xFE, 0xCE,0xCE, 0xCF,0xCF, 0xFF,0xFF, 0xFF,0xFF,
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xDB,0xDB, 0x89,0x89,
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xDB,0xDB, 0x91,0x91,
};
// background: a checkerboard, so the ghost's transparency is visible
/*{w:8,h:8,bpp:1,count:2,brev:1,np:2,pofs:1,sl:2}*/
const uint8_t bg_tiles[] = {
0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
};
#define NBALLS 16
#define GHOST_OAM 36 // OAM slots 36..39 for the ghost
uint8_t ball_x[NBALLS];
void main(void) {
uint8_t i, x, y, frame = 0, rot = 0;
uint8_t row[32];
uint8_t gx = 40;
int8_t gdx = 1;
DISPLAY_OFF;
BGP_REG = 0xE4;
OBP0_REG = 0xE4;
set_bkg_data(0, 2, bg_tiles);
for (y = 0; y < 18; y++) {
for (x = 0; x < 32; x++) row[x] = ((x >> 1) ^ (y >> 1)) & 1;
set_bkg_tiles(0, y, 32, 1, row);
}
set_sprite_data(0, 5, sprite_tiles);
for (i = 0; i < NBALLS; i++) {
ball_x[i] = 12 + i * 9; // 16 balls across 144 pixels
set_sprite_tile(i, 0);
}
set_sprite_tile(GHOST_OAM+0, 1);
set_sprite_tile(GHOST_OAM+1, 2);
set_sprite_tile(GHOST_OAM+2, 3);
set_sprite_tile(GHOST_OAM+3, 4);
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
while (1) {
wait_vbl_done();
frame++;
// --- sprite cycling: ball (i + rot) goes in OAM slot i ---
rot = (rot + 7) & (NBALLS - 1); // step by 7: coprime with 16, so
// every ball gets every slot
for (i = 0; i < NBALLS; i++) {
uint8_t b = (i + rot) & (NBALLS - 1);
move_sprite(i, ball_x[b], 40);
if (++ball_x[b] >= 12 + 144) ball_x[b] = 12; // wrap on screen
}
// --- 30 Hz ghost: visible on even frames only ---
gx += gdx;
if (gx < 16 || gx > 150) gdx = -gdx;
if (frame & 1) {
// hide by moving off the top of the screen (Y = 0)
for (i = 0; i < 4; i++) move_sprite(GHOST_OAM + i, 0, 0);
} else {
move_sprite(GHOST_OAM+0, gx, 96);
move_sprite(GHOST_OAM+1, gx + 8, 96);
move_sprite(GHOST_OAM+2, gx, 104);
move_sprite(GHOST_OAM+3, gx + 8, 104);
}
}
}
+140
View File
@@ -0,0 +1,140 @@
/*
Shared 3D math for the tile-framebuffer demos (wireframe.c, polyfill.c).
Everything here avoids the SM83's missing multiply and divide:
- sin_tab: a 256-step sine table, so an angle is one byte
- mul8(): 8x8 signed multiply from a quarter-square table
- recip[]: 24576/z, so perspective is a multiply instead of a divide
- transform(): rotates and projects the 8 corners of a cube
*/
#ifndef _GB3D_H
#define _GB3D_H
#include <stdint.h>
#include "tilebuf.h"
// sin(a) * 127, a = 0..255 is one full turn
const int8_t sin_tab[256] = {
0,3,6,9,12,16,19,22,25,28,31,34,37,40,43,46,
49,51,54,57,60,63,65,68,71,73,76,78,81,83,85,88,
90,92,94,96,98,100,102,104,106,107,109,111,112,113,115,116,
117,118,120,121,122,122,123,124,125,125,126,126,126,127,127,127,
127,127,127,127,126,126,126,125,125,124,123,122,122,121,120,118,
117,116,115,113,112,111,109,107,106,104,102,100,98,96,94,92,
90,88,85,83,81,78,76,73,71,68,65,63,60,57,54,51,
49,46,43,40,37,34,31,28,25,22,19,16,12,9,6,3,
0,-3,-6,-9,-12,-16,-19,-22,-25,-28,-31,-34,-37,-40,-43,-46,
-49,-51,-54,-57,-60,-63,-65,-68,-71,-73,-76,-78,-81,-83,-85,-88,
-90,-92,-94,-96,-98,-100,-102,-104,-106,-107,-109,-111,-112,-113,-115,-116,
-117,-118,-120,-121,-122,-122,-123,-124,-125,-125,-126,-126,-126,-127,-127,-127,
-127,-127,-127,-127,-126,-126,-126,-125,-125,-124,-123,-122,-122,-121,-120,-118,
-117,-116,-115,-113,-112,-111,-109,-107,-106,-104,-102,-100,-98,-96,-94,-92,
-90,-88,-85,-83,-81,-78,-76,-73,-71,-68,-65,-63,-60,-57,-54,-51,
-49,-46,-43,-40,-37,-34,-31,-28,-25,-22,-19,-16,-12,-9,-6,-3,
};
// quarter squares: sq4[n+256] = floor(n*n/4), n = -256..255
const uint16_t sq4[512] = {
16384,16256,16129,16002,15876,15750,15625,15500,15376,15252,15129,15006,
14884,14762,14641,14520,14400,14280,14161,14042,13924,13806,13689,13572,
13456,13340,13225,13110,12996,12882,12769,12656,12544,12432,12321,12210,
12100,11990,11881,11772,11664,11556,11449,11342,11236,11130,11025,10920,
10816,10712,10609,10506,10404,10302,10201,10100,10000,9900,9801,9702,
9604,9506,9409,9312,9216,9120,9025,8930,8836,8742,8649,8556,
8464,8372,8281,8190,8100,8010,7921,7832,7744,7656,7569,7482,
7396,7310,7225,7140,7056,6972,6889,6806,6724,6642,6561,6480,
6400,6320,6241,6162,6084,6006,5929,5852,5776,5700,5625,5550,
5476,5402,5329,5256,5184,5112,5041,4970,4900,4830,4761,4692,
4624,4556,4489,4422,4356,4290,4225,4160,4096,4032,3969,3906,
3844,3782,3721,3660,3600,3540,3481,3422,3364,3306,3249,3192,
3136,3080,3025,2970,2916,2862,2809,2756,2704,2652,2601,2550,
2500,2450,2401,2352,2304,2256,2209,2162,2116,2070,2025,1980,
1936,1892,1849,1806,1764,1722,1681,1640,1600,1560,1521,1482,
1444,1406,1369,1332,1296,1260,1225,1190,1156,1122,1089,1056,
1024,992,961,930,900,870,841,812,784,756,729,702,
676,650,625,600,576,552,529,506,484,462,441,420,
400,380,361,342,324,306,289,272,256,240,225,210,
196,182,169,156,144,132,121,110,100,90,81,72,
64,56,49,42,36,30,25,20,16,12,9,6,
4,2,1,0,0,0,1,2,4,6,9,12,
16,20,25,30,36,42,49,56,64,72,81,90,
100,110,121,132,144,156,169,182,196,210,225,240,
256,272,289,306,324,342,361,380,400,420,441,462,
484,506,529,552,576,600,625,650,676,702,729,756,
784,812,841,870,900,930,961,992,1024,1056,1089,1122,
1156,1190,1225,1260,1296,1332,1369,1406,1444,1482,1521,1560,
1600,1640,1681,1722,1764,1806,1849,1892,1936,1980,2025,2070,
2116,2162,2209,2256,2304,2352,2401,2450,2500,2550,2601,2652,
2704,2756,2809,2862,2916,2970,3025,3080,3136,3192,3249,3306,
3364,3422,3481,3540,3600,3660,3721,3782,3844,3906,3969,4032,
4096,4160,4225,4290,4356,4422,4489,4556,4624,4692,4761,4830,
4900,4970,5041,5112,5184,5256,5329,5402,5476,5550,5625,5700,
5776,5852,5929,6006,6084,6162,6241,6320,6400,6480,6561,6642,
6724,6806,6889,6972,7056,7140,7225,7310,7396,7482,7569,7656,
7744,7832,7921,8010,8100,8190,8281,8372,8464,8556,8649,8742,
8836,8930,9025,9120,9216,9312,9409,9506,9604,9702,9801,9900,
10000,10100,10201,10302,10404,10506,10609,10712,10816,10920,11025,11130,
11236,11342,11449,11556,11664,11772,11881,11990,12100,12210,12321,12432,
12544,12656,12769,12882,12996,13110,13225,13340,13456,13572,13689,13806,
13924,14042,14161,14280,14400,14520,14641,14762,14884,15006,15129,15252,
15376,15500,15625,15750,15876,16002,16129,16256,
};
// Quarter-square multiplication:
// a*b = ((a+b)^2 - (a-b)^2) / 4
// With a table of floor(n^2/4), an 8x8 signed multiply costs two table
// lookups and a subtraction. (The floors cancel: a+b and a-b are always
// both even or both odd.)
int16_t mul8(int8_t a, int8_t b) {
return sq4[256 + a + b] - sq4[256 + a - b];
}
// Perspective divide by table: recip[z] = 24576 / z, built once at boot
// with the slow library divide. At run time x/z becomes x * recip[z].
uint16_t recip[256];
#define ZDIST 160 // distance from eye to cube center
#define CX (TB_W/2)
#define CY (TB_H/2)
// cube corners
const int8_t cube[8][3] = {
{-32,-32,-32}, { 32,-32,-32}, { 32, 32,-32}, {-32, 32,-32},
{-32,-32, 32}, { 32,-32, 32}, { 32, 32, 32}, {-32, 32, 32},
};
// 12 edges as pairs of corner indices
const uint8_t edges[12][2] = {
{0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7},
};
uint8_t sx[8], sy[8]; // projected corners
void transform(uint8_t ay, uint8_t ax) {
int8_t sa = sin_tab[ay], ca = sin_tab[(uint8_t)(ay + 64)];
int8_t sb = sin_tab[ax], cb = sin_tab[(uint8_t)(ax + 64)];
uint8_t i;
for (i = 0; i < 8; i++) {
int8_t x = cube[i][0], y = cube[i][1], z = cube[i][2];
int8_t x1, z1, y2, z2;
uint8_t zz;
// rotate around Y (results scaled back down by 127 ~ >>7)
x1 = (mul8(x, ca) - mul8(z, sa)) >> 7;
z1 = (mul8(x, sa) + mul8(z, ca)) >> 7;
// rotate around X
y2 = (mul8(y, cb) - mul8(z1, sb)) >> 7;
z2 = (mul8(y, sb) + mul8(z1, cb)) >> 7;
// project: screen = center + x * (96 / z)
zz = ZDIST + z2;
sx[i] = CX + (int8_t)(((int16_t)x1 * (int16_t)recip[zz]) >> 8);
sy[i] = CY + (int8_t)(((int16_t)y2 * (int16_t)recip[zz]) >> 8);
}
}
// call once at startup: fill recip[] using the (slow) library divide
void gb3d_init(void) {
uint16_t z;
for (z = 1; z < 256; z++) recip[z] = 24576U / z;
}
#endif
+170
View File
@@ -0,0 +1,170 @@
/*
Parallax scrolling with one background layer.
The Game Boy has one scrolling background (plus the non-scrolling-ish
Window), so there is no second "far" layer to scroll at a different
speed. Games fake one in two ways, and this demo does both:
1. Raster splits. The screen is cut into horizontal bands. An LY==LYC
interrupt fires just before each band starts and rewrites SCX, so
each band scrolls at its own speed: clouds at 1/8, mountains at 1/4,
trees at 1/2, the ground at full speed.
2. Tile animation. The water band at the bottom never scrolls (SCX = 0
there), but its one tile's pixels are rotated a bit each frame, so the
whole band appears to move. This works anywhere -- even in the middle
of a band scrolling at another speed -- because every copy of a tile
on screen changes at once. The cost: the rotated tile can't be used
anywhere else.
Chikyu Kaiho Gun ZAS (T&E Soft, 1992) and Shantae (WayForward, 2002) are
both known for parallax on this hardware; this demo shows the general
technique, not their code.
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
// ---------------------------------------------------------------------
// tiles: each row is two bytes, low bit-plane then high bit-plane
#define T_SKY 0
#define T_CLOUDL 1
#define T_CLOUD 2
#define T_CLOUDR 3
#define T_MTNL 4
#define T_MTNR 5
#define T_MTN 6
#define T_TREE 7
#define T_TRUNK 8
#define T_GRASS 9
#define T_DIRT 10
#define T_WATER 11
/*{w:8,h:8,bpp:1,count:12,brev:1,np:2,pofs:1,sl:2}*/
const uint8_t tiles[] = {
// T_SKY: color 0
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
// T_CLOUDL
0x00,0x00, 0x07,0x00, 0x1F,0x00, 0x3F,0x00, 0x7F,0x00, 0xFF,0x00, 0x7F,0x00, 0x00,0x00,
// T_CLOUD
0x3C,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0x00,0x00,
// T_CLOUDR
0x00,0x00, 0xE0,0x00, 0xF8,0x00, 0xFC,0x00, 0xFE,0x00, 0xFF,0x00, 0xFE,0x00, 0x00,0x00,
// T_MTNL: '/' edge, color 2 below
0x00,0x01, 0x00,0x03, 0x00,0x07, 0x00,0x0F, 0x00,0x1F, 0x00,0x3F, 0x00,0x7F, 0x00,0xFF,
// T_MTNR: '\' edge
0x00,0x80, 0x00,0xC0, 0x00,0xE0, 0x00,0xF0, 0x00,0xF8, 0x00,0xFC, 0x00,0xFE, 0x00,0xFF,
// T_MTN: solid color 2, a little snow-line texture
0x00,0xFF, 0x00,0xFF, 0x22,0xFF, 0x00,0xFF, 0x00,0xFF, 0x88,0xFF, 0x00,0xFF, 0x00,0xFF,
// T_TREE: round top, color 3
0x3C,0x3C, 0x7E,0x7E, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0x7E,0x7E, 0x3C,0x3C,
// T_TRUNK
0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x3C,0x3C, 0xFF,0xFF,
// T_GRASS: color 1 with blades of color 3
0x00,0x00, 0x44,0x44, 0xEE,0xEE, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
// T_DIRT: color 2 with color 3 pebbles
0x00,0xFF, 0x40,0xFF, 0x00,0xFF, 0x04,0xFF, 0x00,0xFF, 0x20,0xFF, 0x00,0xFF, 0x02,0xFF,
// T_WATER: waves (animated by rotating each row's bits)
0x00,0xFF, 0x00,0xFF, 0xC3,0x3C, 0xFF,0x00, 0x00,0xFF, 0x00,0xFF, 0x3C,0xC3, 0xFF,0x00,
};
// ---------------------------------------------------------------------
// the map: an 8-tile-wide pattern repeated across all 32 columns, so the
// 256-pixel background wraps seamlessly as SCX rolls over
const uint8_t pattern[18][8] = {
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY },
{ T_SKY,T_CLOUDL,T_CLOUD,T_CLOUDR,T_SKY,T_SKY,T_SKY,T_SKY },
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_CLOUDL,T_CLOUDR,T_SKY },
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY },
{ T_SKY,T_SKY,T_SKY,T_MTNL,T_MTNR,T_SKY,T_SKY,T_SKY },
{ T_SKY,T_SKY,T_MTNL,T_MTN,T_MTN,T_MTNR,T_SKY,T_SKY },
{ T_SKY,T_MTNL,T_MTN,T_MTN,T_MTN,T_MTN,T_MTNR,T_SKY },
{ T_MTNL,T_MTN,T_MTN,T_MTN,T_MTN,T_MTN,T_MTN,T_MTNR },
{ T_SKY,T_TREE,T_SKY,T_SKY,T_TREE,T_SKY,T_TREE,T_SKY },
{ T_SKY,T_TRUNK,T_SKY,T_SKY,T_TRUNK,T_SKY,T_TRUNK,T_SKY },
{ T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS },
{ T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT },
{ T_TREE,T_SKY,T_SKY,T_TREE,T_SKY,T_SKY,T_SKY,T_SKY },
{ T_TRUNK,T_SKY,T_SKY,T_TRUNK,T_SKY,T_SKY,T_SKY,T_SKY },
{ T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS },
{ T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT },
{ T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER },
{ T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER },
};
// ---------------------------------------------------------------------
// raster bands
#define NBANDS 5
// LYC value for each band: the line *before* the band starts, so the
// handler has time to wait for HBlank and write SCX before it's drawn
const uint8_t band_lyc[NBANDS] = { 0, 31, 63, 95, 127 };
uint8_t band_scx[NBANDS]; // SCX for each band, set by main()
volatile uint8_t band; // band the raster is in
void vbl_isr(void) {
// top of the frame: band 0 and arm the first split
band = 0;
SCX_REG = band_scx[0];
LYC_REG = band_lyc[1];
}
void lcd_isr(void) {
// we're at the start of the line before the next band; wait for
// HBlank so the change doesn't land in the middle of a visible line
while (STAT_REG & 3) ;
band++;
SCX_REG = band_scx[band];
// arm the next split, or park LYC below the screen
LYC_REG = (band < NBANDS-1) ? band_lyc[band+1] : 200;
}
void main(void) {
uint8_t x, y, i;
uint16_t pos = 0;
uint8_t water[16];
DISPLAY_OFF;
BGP_REG = 0xE4;
set_bkg_data(0, sizeof(tiles)/16, tiles);
for (y = 0; y < 18; y++) {
for (x = 0; x < 32; x += 8) {
set_bkg_tiles(x, y, 8, 1, pattern[y]);
}
}
for (i = 0; i < 16; i++) water[i] = tiles[T_WATER*16 + i];
STAT_REG = STATF_LYC;
LYC_REG = band_lyc[1];
disable_interrupts();
add_VBL(vbl_isr);
add_LCD(lcd_isr);
enable_interrupts();
set_interrupts(VBL_IFLAG | LCD_IFLAG);
SHOW_BKG;
DISPLAY_ON;
while (1) {
wait_vbl_done();
// 1. new scroll positions for next frame
pos++;
band_scx[0] = pos >> 3; // clouds
band_scx[1] = pos >> 2; // mountains
band_scx[2] = pos >> 1; // far trees
band_scx[3] = pos; // near ground
band_scx[4] = 0; // water: fixed, animated below
// 2. animate the water tile: rotate every row one pixel left
// (every other frame, so it moves at half speed)
if (pos & 1) {
for (i = 0; i < 16; i++) {
uint8_t b = water[i];
water[i] = (b << 1) | (b >> 7);
}
set_bkg_data(T_WATER, 1, water);
}
}
}
+117
View File
@@ -0,0 +1,117 @@
/*
Filled, dithered polygons in a tile framebuffer, in the style of
Race Drivin' (Argonaut, 1993).
The same spinning cube as wireframe.c, but solid:
1. back-face culling: a face whose corners appear clockwise on screen is
facing away, so it is skipped -- on a convex object that is all the
hidden-surface removal we need
2. each visible face is scan-converted into a table of left/right X
coordinates, one pair per pixel row
3. each row is filled a *byte* at a time: a masked partial byte at each
end, and plain writes for the 8-pixel runs in between
4. shading: 4 gray levels plus checkerboard dithers between neighbors
give 7 shades; the more a face points at the viewer, the lighter it is
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "tilebuf.h"
#include "gb3d.h"
// the six faces, corners listed in the same winding order
const uint8_t faces[6][4] = {
{0,1,2,3}, {5,4,7,6}, {4,0,3,7}, {1,5,6,2}, {4,5,1,0}, {3,2,6,7},
};
// ---------------------------------------------------------------------
// scan conversion
uint8_t xleft[TB_H], xright[TB_H];
uint8_t ytop, ybot;
// Walk one edge with an integer error term (no divide), recording the
// X at every row into both the left and right tables: the smaller X
// ends up in xleft[], the larger in xright[].
void edge(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
uint8_t dx, dy, y, x;
int8_t xdir;
int16_t err;
if (y0 > y1) {
y = x0; x0 = x1; x1 = y;
y = y0; y0 = y1; y1 = y;
}
if (y0 < ytop) ytop = y0;
if (y1 > ybot) ybot = y1;
dy = y1 - y0;
if (x1 >= x0) { dx = x1 - x0; xdir = 1; }
else { dx = x0 - x1; xdir = -1; }
x = x0;
err = dy >> 1;
for (y = y0; ; y++) {
if (x < xleft[y]) xleft[y] = x;
if (x > xright[y]) xright[y] = x;
if (y == y1) break;
// advance X until the error term says it's time for the next row
err -= dx;
while (err < 0) {
err += dy;
x += xdir;
if (x < xleft[y]) xleft[y] = x;
if (x > xright[y]) xright[y] = x;
}
}
}
void fill_face(const uint8_t* f, const Shade* s) {
uint8_t y;
ytop = TB_H - 1; ybot = 0;
for (y = 0; y < TB_H; y++) { xleft[y] = 255; xright[y] = 0; }
edge(sx[f[0]], sy[f[0]], sx[f[1]], sy[f[1]]);
edge(sx[f[1]], sy[f[1]], sx[f[2]], sy[f[2]]);
edge(sx[f[2]], sy[f[2]], sx[f[3]], sy[f[3]]);
edge(sx[f[3]], sy[f[3]], sx[f[0]], sy[f[0]]);
for (y = ytop; y <= ybot; y++) {
span(y, xleft[y], xright[y], s);
}
}
// Twice the signed screen area of triangle (a,b,c): positive when the
// corners run counter-clockwise, i.e. the face points toward us. Its
// size also says how squarely the face points at us, which makes a
// free light source "behind the camera".
int16_t facing(const uint8_t* f) {
int16_t ax = sx[f[1]] - sx[f[0]], ay = sy[f[1]] - sy[f[0]];
int16_t bx = sx[f[2]] - sx[f[0]], by = sy[f[2]] - sy[f[0]];
return ax * by - ay * bx;
}
void main(void) {
uint8_t ay = 0, ax = 0, i, y;
DISPLAY_OFF;
BGP_REG = 0xE4;
gb3d_init();
tb_init();
DISPLAY_ON;
while (1) {
// ground: dithered lower half, like a road-racer horizon
for (y = TB_H/2 + 20; y < TB_H; y++) span(y, 0, TB_W - 1, &shades[1]);
transform(ay, ax);
for (i = 0; i < 6; i++) {
int16_t area = facing(faces[i]);
if (area > 0) {
// bigger area = facing us more squarely = lighter shade
// (faces use shades 3..6 so they never match the ground's shade 1)
int16_t d = area / 600;
uint8_t shade = (d >= 3) ? 3 : 6 - d;
fill_face(faces[i], &shades[shade]);
}
}
tb_blit();
ay += 3;
ax += 2;
}
}
+2 -1
View File
@@ -42,7 +42,8 @@ void lcd_handler(void) NONBANKED {
uint8_t ly = LY_REG;
if (ly < 143) {
// offset this line and arm the interrupt for the next one
SCX_REG = 248 + (int8_t)wave[(ly + phase) & 31];
// (ly advances 8 lines per interrupt, so ly >> 3 steps one table entry)
SCX_REG = 248 + (int8_t)wave[((ly >> 3) + phase) & 31];
LYC_REG = ly + 8;
} else {
// last line: reset so the next frame starts at the top
+265
View File
@@ -0,0 +1,265 @@
/*
A grid raycaster in a tile framebuffer, in the spirit of Faceball 2000
(Xanth Software F/X, 1991) and Wolfenstein 3D.
The world is a 16x16 grid of cells. For each 4-pixel-wide column of the
144x112 viewport we cast one ray from the player through the grid, one
cell boundary at a time (the "DDA" walk), until it hits a wall. The
distance to the wall sets the height of that column's wall slice.
Why this suits the Game Boy:
- only 36 rays per frame, and each ray visits a few cells
- walls are vertical spans centered on the horizon, so every pixel is
written exactly once -- no sorting, no overdraw, no buffer clear needed
- flat shading: walls facing east/west get one shade, north/south
another -- the same cheap trick Faceball and Wolfenstein use
Controls: left/right to turn, up/down to move. With no input, the
player slowly turns in place.
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "tilebuf.h"
#include "gb3d.h"
#define NCOLS (TB_W/4) // 36 rays, 4 pixels wide each
const uint8_t map[16][16] = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1},
{1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
// player position in 8.8 fixed point (high byte = map cell)
uint16_t px = 0x0880, py = 0x0880;
uint8_t pa = 0; // facing angle, 256 = full turn
// camera-plane offset for each column, -80..+80 (a ~64 degree view)
int8_t camx[NCOLS];
// inv[r] = 32512 / r: the distance along a ray between two grid lines,
// in 8.8 cells, for a ray whose X (or Y) component is r/127
uint16_t inv[256];
// wall half-height for each distance: htab[d >> 3] = (TB_H/2) / d,
// with d in 8.8 cells -- a table instead of a divide per column
uint8_t htab[512];
// wall slice for each column, computed by cast()
uint8_t wall_h[NCOLS]; // half-height, 0..TB_H/2
uint8_t wall_side[NCOLS]; // 0 = east/west face, 1 = north/south face
// distance to the first grid line = fraction of a cell * ddx.
// ddx is 8.8 and frac is 0..256, so pre-shift both to stay in 16 bits.
uint16_t first_side(uint16_t frac, uint16_t dd) {
if (dd >= 0x1000) return 0xffff; // ray nearly parallel: never
return ((dd >> 4) * frac) >> 4;
}
void cast(void) {
int8_t dirx = sin_tab[(uint8_t)(pa + 64)]; // cos(pa)
int8_t diry = sin_tab[pa]; // sin(pa)
uint8_t col;
for (col = 0; col < NCOLS; col++) {
// ray direction = view direction + camera plane * camx
int16_t rx = dirx - (mul8(diry, camx[col]) >> 7);
int16_t ry = diry + (mul8(dirx, camx[col]) >> 7);
uint8_t mx = px >> 8, my = py >> 8;
uint8_t arx = rx < 0 ? -rx : rx, ary = ry < 0 ? -ry : ry;
uint16_t ddx = arx ? inv[arx] : 0xffff;
uint16_t ddy = ary ? inv[ary] : 0xffff;
uint16_t sdx, sdy, dist;
int8_t stepx, stepy;
uint8_t side;
// distance to the first vertical and horizontal grid lines
if (rx < 0) { stepx = -1; sdx = first_side(px & 0xff, ddx); }
else { stepx = 1; sdx = first_side(256 - (px & 0xff), ddx); }
if (ry < 0) { stepy = -1; sdy = first_side(py & 0xff, ddy); }
else { stepy = 1; sdy = first_side(256 - (py & 0xff), ddy); }
// DDA: step to whichever grid line is nearer, until we hit a wall
while (1) {
if (sdx < sdy) { sdx += ddx; mx += stepx; side = 0; }
else { sdy += ddy; my += stepy; side = 1; }
if (map[my][mx]) break;
}
// perpendicular distance (no fish-eye) in 8.8 cells
dist = side ? sdy - ddy : sdx - ddx;
dist >>= 3;
wall_h[col] = dist < 512 ? htab[dist] : 0;
wall_side[col] = side;
}
}
// ---------------------------------------------------------------------
// drawing
//
// Two 4-pixel columns share each byte, so drawing one column at a time
// means a read-modify-write for every row -- far too slow in C. Instead
// we draw columns in *pairs*, top to bottom, writing whole bytes. Because
// walls are centered on the horizon, each pair splits into at most five
// bands where the byte pattern doesn't change:
//
// ceiling | taller wall + ceiling | both walls | taller wall + floor | floor
//
// so we compute one pattern per band and blast it down the rows.
uint8_t *g_p; // current framebuffer byte
uint8_t g_y; // current row
uint8_t g_n; // rows to fill
uint8_t ple, phe, plo, pho; // pattern: even-row lo/hi, odd-row lo/hi
// Write g_n rows of the current pattern, starting at g_p / row g_y.
//
// This is the innermost loop of the renderer (18 pairs x 112 rows =
// 2016 rows per frame), so it's in assembly: about 20 cycles per row,
// versus about 120 for the same loop compiled from C.
void fill_rows(void) __naked {
__asm
ld a, (_g_n)
or a, a
ret z
ld b, a ; B = row count
ld a, (_g_p)
ld l, a
ld a, (_g_p+1)
ld h, a ; HL = framebuffer pointer
ld a, (_g_y)
ld c, a ; C = row number
ld a, (_ple)
ld d, a
ld a, (_phe)
ld e, a ; DE = even-row pattern
1$:
bit 0, c
jr nz, 2$
ld a, d ; even row
ld (hl+), a
ld a, e
ld (hl+), a
jr 3$
2$:
ld a, (_plo) ; odd row
ld (hl+), a
ld a, (_pho)
ld (hl+), a
3$:
ld a, c
inc c
and a, #7
cp a, #7
jr nz, 4$
; last row of a tile: skip to the next row of tiles
; (+288 bytes per tile row, minus the 16 we walked through)
ld a, l
add a, #<(288-16)
ld l, a
ld a, h
adc a, #>(288-16)
ld h, a
4$:
dec b
jr nz, 1$
ld a, l
ld (_g_p), a
ld a, h
ld (_g_p+1), a
ld a, c
ld (_g_y), a
ret
__endasm;
}
#define CEIL 0 // shades[] index for ceiling
#define FLOOR 1 // shades[] index for floor
#define WALL_EW 6 // east/west walls
#define WALL_NS 4 // north/south walls
// pattern bytes for a left/right nibble pair of two shades
void pair_pattern(uint8_t sl, uint8_t sr) {
const Shade* a = &shades[sl];
const Shade* b = &shades[sr];
ple = (a->lo0 & 0xf0) | (b->lo0 & 0x0f);
phe = (a->hi0 & 0xf0) | (b->hi0 & 0x0f);
plo = (a->lo1 & 0xf0) | (b->lo1 & 0x0f);
pho = (a->hi1 & 0xf0) | (b->hi1 & 0x0f);
}
void draw_pair(uint8_t pair) {
uint8_t c = pair << 1;
uint8_t hl = wall_h[c], hr = wall_h[c+1];
uint8_t wl = wall_side[c] ? WALL_NS : WALL_EW;
uint8_t wr = wall_side[c+1] ? WALL_NS : WALL_EW;
uint8_t hs = hl < hr ? hl : hr; // shorter wall
uint8_t ht = hl < hr ? hr : hl; // taller wall
g_p = tb_buf + (pair << 4);
g_y = 0;
// 1. ceiling over both
pair_pattern(CEIL, CEIL);
g_n = TB_H/2 - ht; fill_rows();
// 2. taller wall beside ceiling
if (hl > hr) pair_pattern(wl, CEIL); else pair_pattern(CEIL, wr);
g_n = ht - hs; fill_rows();
// 3. both walls, across the horizon
pair_pattern(wl, wr);
g_n = hs * 2; fill_rows();
// 4. taller wall beside floor
if (hl > hr) pair_pattern(wl, FLOOR); else pair_pattern(FLOOR, wr);
g_n = ht - hs; fill_rows();
// 5. floor under both
pair_pattern(FLOOR, FLOOR);
g_n = TB_H/2 - ht; fill_rows();
}
void render(void) {
uint8_t pair;
for (pair = 0; pair < NCOLS/2; pair++) draw_pair(pair);
}
void move(int8_t speed) {
// move along the facing direction, if the destination cell is open
uint16_t nx = px + (((int16_t)sin_tab[(uint8_t)(pa + 64)] * speed) >> 5);
uint16_t ny = py + (((int16_t)sin_tab[pa] * speed) >> 5);
if (!map[ny >> 8][nx >> 8]) { px = nx; py = ny; }
}
void main(void) {
uint8_t i;
uint16_t d;
DISPLAY_OFF;
BGP_REG = 0xE4;
for (i = 1; i != 0; i++) inv[i] = 32512U / i;
for (d = 0; d < 512; d++) {
uint16_t h = d ? (TB_H/2 * 32U) / d : 255; // (TB_H/2) / (d*8/256)
htab[d] = h > TB_H/2 ? TB_H/2 : h;
}
for (i = 0; i < NCOLS; i++) camx[i] = (int8_t)(((int16_t)i * 160) / (NCOLS - 1) - 80);
tb_init();
DISPLAY_ON;
while (1) {
uint8_t j = joypad();
if (j & J_LEFT) pa -= 4;
else if (j & J_RIGHT) pa += 4;
else if (!j) pa += 2;
if (j & J_UP) move(4);
if (j & J_DOWN) move(-4);
cast();
render();
tb_blit();
}
}
+156
View File
@@ -0,0 +1,156 @@
/*
Battery-backed save RAM for Game Boy.
The cartridge header declares MBC5 + RAM + battery, with 8 KB of RAM
mapped at 0xA000-0xBFFF. The RAM is switched off at power-up; we
write 0x0A to 0x0000 to enable it and anything else to disable it.
The save is a small record with a signature and a checksum, so that
a fresh cartridge (or one whose battery has died) is not mistaken
for a real save:
0xA000 "GBSV" signature
0xA004 boots how many times the game has been started
0xA006 best best score
0xA008 check sum of the bytes above, plus 0x5A
Controls: A adds a point, Start saves the score if it's a new best,
B erases the save. Reset the emulator (or switch a real console off
and on) and the numbers come back.
*/
//#symbol ld GB_MAPPER=0x1B
//#symbol ld GB_RAM_SIZE=0x02
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "gbtext.h"
typedef struct {
char magic[4];
uint16_t boots;
uint16_t best;
uint8_t check;
} Save;
#define SAVE ((volatile Save*)0xA000)
#define ENABLE_SAVE_RAM (*(volatile uint8_t*)0x0000 = 0x0A)
#define DISABLE_SAVE_RAM (*(volatile uint8_t*)0x0000 = 0x00)
// sum of every byte of the record except the checksum itself
static uint8_t save_sum(void) {
volatile uint8_t* p = (volatile uint8_t*)SAVE;
uint8_t i, sum = 0x5A;
for (i = 0; i < sizeof(Save) - 1; i++) sum += p[i];
return sum;
}
static uint8_t save_valid(void) {
return SAVE->magic[0] == 'G' && SAVE->magic[1] == 'B' &&
SAVE->magic[2] == 'S' && SAVE->magic[3] == 'V' &&
SAVE->check == save_sum();
}
// RAM must be enabled by the caller
static void save_reset(void) {
SAVE->magic[0] = 'G'; SAVE->magic[1] = 'B';
SAVE->magic[2] = 'S'; SAVE->magic[3] = 'V';
SAVE->boots = 0;
SAVE->best = 0;
SAVE->check = save_sum();
}
// format a number as five digits (the division is slow, so
// we do this while the screen is drawing, not during VBlank)
static void fmt_num(char* buf, uint16_t v) {
uint8_t i;
for (i = 5; i > 0; i--) {
buf[i - 1] = '0' + v % 10;
v /= 10;
}
buf[5] = '\0';
}
void main(void) {
char bootbuf[6], bestbuf[6], scorebuf[6];
const char* msg;
uint16_t score = 0, best;
uint8_t keys, prev = 0, fresh, dirty;
DISPLAY_OFF;
BGP_REG = 0xE4;
font_init();
// load the save, or start a new one
ENABLE_SAVE_RAM;
fresh = !save_valid();
if (fresh) save_reset();
SAVE->boots++;
SAVE->check = save_sum();
fmt_num(bootbuf, SAVE->boots);
best = SAVE->best;
DISABLE_SAVE_RAM;
fmt_num(bestbuf, best);
fmt_num(scorebuf, score);
put_str(5, 2, "SAVE RAM");
put_str(2, 5, "BOOTS");
put_str(2, 7, "BEST");
put_str(2, 9, "SCORE");
put_str(2, 12, fresh ? "NEW SAVE" : "SAVE LOADED");
put_str(2, 15, "A ADD START SAVE");
put_str(2, 16, "B ERASE");
put_str(9, 5, bootbuf);
put_str(9, 7, bestbuf);
put_str(9, 9, scorebuf);
SHOW_BKG;
DISPLAY_ON;
while (1) {
keys = joypad();
dirty = 0;
msg = 0;
if ((keys & J_A) && !(prev & J_A)) {
score++;
dirty = 1;
}
if ((keys & J_START) && !(prev & J_START) && score > best) {
best = score;
ENABLE_SAVE_RAM;
SAVE->best = best;
SAVE->check = save_sum();
DISABLE_SAVE_RAM;
msg = "SAVED ";
dirty = 1;
}
if ((keys & J_B) && !(prev & J_B)) {
ENABLE_SAVE_RAM;
save_reset();
DISABLE_SAVE_RAM;
best = 0;
score = 0;
msg = "ERASED ";
dirty = 1;
}
prev = keys;
if (dirty) {
fmt_num(bestbuf, best);
fmt_num(scorebuf, score);
}
wait_vbl_done();
if (dirty) {
put_str(9, 7, bestbuf);
put_str(9, 9, scorebuf);
if (msg) put_str(2, 12, msg);
}
}
}
+106
View File
@@ -0,0 +1,106 @@
/*
Assembly language on the SM83.
The same loop -- add up N bytes -- written twice: once in C, and once
by hand as a __naked function. The timer counts how long each version
takes, so we can see what the compiler's version costs.
SDCC passes arguments on the stack (the first at SP+2 once the return
address is pushed), and an 8-bit result comes back in E. A __naked
function gets no prologue or epilogue at all, so the hand-written
version has to end with its own ret.
TAC = 5 makes TIMA tick at 262144 Hz: once every 16 clocks, which is
4 machine cycles. TIMA is only 8 bits wide, so keep the runs short
enough that it can't wrap (256 ticks = 1024 machine cycles).
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "gbtext.h"
#define COUNT 16
uint8_t sum_c(const uint8_t *p, uint8_t n) {
uint8_t s = 0;
while (n--) s += *p++;
return s;
}
uint8_t sum_asm(const uint8_t *p, uint8_t n) __naked {
p; n; // (silences unused-argument warnings)
__asm
ldhl sp,#2 ; HL -> first argument
ld a,(hl+)
ld e,a
ld a,(hl+)
ld d,a ; DE = p
ld b,(hl) ; B = n
ld h,d
ld l,e ; HL = p
xor a,a ; A = 0
inc b ; enter the loop at the test, so n = 0 works
jr 2$
1$: add a,(hl) ; A += *p
inc hl ; p++
2$: dec b
jr nz,1$
ld e,a ; the result goes in E
ret
__endasm;
}
uint8_t buf[COUNT];
// clear the timer, run one call, and return the ticks it took
#define TIME(var, expr) \
do { TIMA_REG = 0; sink = (expr); var = TIMA_REG; } while (0)
volatile uint8_t sink;
static void put_dec(uint8_t x, uint8_t y, uint16_t v) {
char s[6];
uint8_t i = 5;
s[5] = '\0';
do { s[--i] = '0' + v % 10; v /= 10; } while (v);
put_str(x, y, s + i);
}
void main(void) {
uint8_t i, c0, c1, a0, a1, same;
DISPLAY_OFF;
BGP_REG = 0xE4;
font_init();
for (i = 0; i < COUNT; i++) buf[i] = i * 3;
TMA_REG = 0;
TAC_REG = 0x05; // enabled, 262144 Hz
TIME(c0, sum_c(buf, 0));
TIME(c1, sum_c(buf, COUNT));
TIME(a0, sum_asm(buf, 0));
TIME(a1, sum_asm(buf, COUNT));
same = sum_c(buf, COUNT) == sum_asm(buf, COUNT);
put_str(2, 1, "C VS ASSEMBLY");
put_str(1, 4, "SAME ANSWER");
put_str(14, 4, same ? "YES" : "NO");
put_str(1, 7, "C ");
put_str(1, 8, "ASM");
put_str(6, 6, "TICKS");
put_dec(7, 7, c1 - c0);
put_dec(7, 8, a1 - a0);
put_str(1, 11, "CYCLES PER BYTE");
// one tick is 4 machine cycles (rounded; TIMA can be off by one tick)
put_str(1, 12, "C ");
put_str(1, 13, "ASM");
put_dec(7, 12, ((uint16_t)(c1 - c0) * 4 + COUNT / 2) / COUNT);
put_dec(7, 13, ((uint16_t)(a1 - a0) * 4 + COUNT / 2) / COUNT);
SHOW_BKG;
DISPLAY_ON;
while (1) wait_vbl_done();
}
+260
View File
@@ -0,0 +1,260 @@
/*
A software framebuffer made of background tiles.
The Game Boy has no bitmap mode, but if every cell in a rectangle of the
tile map points at a *different* tile, then each pixel in that rectangle
belongs to exactly one bit of one tile -- and the tiles become a bitmap.
This header uses the layout from Faceball 2000 (1991):
- The viewport is 18x14 tiles (144x112 pixels): 252 unique tiles.
- The background uses the $8800 "signed" tile-data mode (LCDC bit 4 = 0).
In that mode tile numbers $80..$FF live at $8800..$8FFF and $00..$7F at
$9000..$97FF, so counting upward from tile $84 and wrapping past $FF
gives one *linear* run of VRAM: $8840..$983F. One copy loop fills it.
- Tiles $80..$83 are left over for the border around the viewport.
- The CPU draws into a 4032-byte buffer in WRAM (tb_buf), which has the
same tile-by-tile layout as VRAM, then tb_blit() copies it to VRAM
eight bytes per scanline, during HBlank, while the screen is on.
The price is VRAM bandwidth: 4032 bytes / 8 per line = 504 scanlines,
about 3.3 frames just to copy one picture. That is why 3D games on the
original Game Boy ran at single-digit frame rates.
*/
#ifndef _TILEBUF_H
#define _TILEBUF_H
#include <stdint.h>
#define TB_COLS 18 // viewport width in tiles
#define TB_ROWS 14 // viewport height in tiles
#define TB_W (TB_COLS*8) // 144 pixels
#define TB_H (TB_ROWS*8) // 112 pixels
#define TB_ROWBYTES (TB_COLS*16) // 288 bytes per row of tiles
#define TB_BYTES (TB_ROWS*TB_ROWBYTES) // 4032
#define TB_FIRST_TILE 0x84 // first viewport tile number
#define TB_VRAM ((uint8_t*)0x8840) // tile $84 in $8800 mode
#define TB_MAPX 1 // viewport position on the tile map
#define TB_MAPY 1
// the framebuffer, in WRAM, laid out exactly like the VRAM tiles
uint8_t tb_buf[TB_BYTES];
// address of pixel row y, column 0 -- Faceball keeps this same
// table at $0300 in its ROM ("FrameBufferRAMOffsets")
uint8_t* tb_rowaddr[TB_H];
// bit mask for each pixel column within a byte
const uint8_t tb_bit[8] = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 };
// ---------------------------------------------------------------------
// setup
// Border tiles: $80 blank, $81 horizontal edge, $82 vertical edge.
/*{w:8,h:8,bpp:1,count:3,brev:1,np:2,pofs:1,sl:2}*/
static const uint8_t tb_border_tiles[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
void tb_init(void) {
uint8_t x, y, t;
uint8_t row[TB_COLS];
// build the row-address table
for (y = 0; y < TB_H; y++) {
tb_rowaddr[y] = tb_buf + (y >> 3) * TB_ROWBYTES + (y & 7) * 2;
}
// border tiles live in the $8800 half, tile numbers $80..$82
set_bkg_data(0x80, 3, tb_border_tiles);
fill_bkg_rect(0, 0, 20, 18, 0x80);
fill_bkg_rect(TB_MAPX, 0, TB_COLS, 1, 0x81);
fill_bkg_rect(0, TB_MAPY, 1, TB_ROWS, 0x82);
// number the viewport cells $84, $85, ... $FF, $00, ... $7F
t = TB_FIRST_TILE;
for (y = 0; y < TB_ROWS; y++) {
for (x = 0; x < TB_COLS; x++) row[x] = t++;
set_bkg_tiles(TB_MAPX, TB_MAPY + y, TB_COLS, 1, row);
}
// clear buffer and VRAM
for (x = 0; x < TB_ROWS; x++) {
uint8_t* p = tb_buf + x * TB_ROWBYTES;
uint16_t i;
for (i = 0; i < TB_ROWBYTES; i++) p[i] = 0;
}
// $8800 addressing: LCDC bit 4 clear
LCDC_REG = LCDCF_ON | LCDCF_BG8800 | LCDCF_BG9800 | LCDCF_BGON;
}
// ---------------------------------------------------------------------
// the blitter
// Interrupts need a real stack while tb_blit() is borrowing SP,
// so we give them this one (Faceball uses $C9A1).
static uint8_t tb_isr_stack[96];
static uint16_t tb_saved_sp;
/*
Copy tb_buf to VRAM, 8 bytes per scanline, and clear tb_buf behind us.
This is a transcription of Faceball 2000's UpdateFrameBuffer routine
($13F0 in the ROM). The trick is the stack pointer: POP BC reads two
bytes and advances the pointer in 3 cycles, the fastest way to read
memory on the SM83. So SP is pointed at the buffer and the loop pops
its way through it:
1. POP the first two bytes before we wait (saves time in HBlank)
2. wait until STAT bit 1 is set (mode 2/3: the PPU is drawing)
3. wait until STAT bit 1 is clear (mode 0: HBlank -- VRAM is free)
4. write 8 bytes to VRAM. Mode 0 plus the following mode 2 (VRAM is
only locked in mode 3) gives at least ~40 M-cycles, enough for 8.
5. park SP on a safe stack and enable interrupts while the PPU draws
the next line, so the VBlank handler/music can still run
6. PUSH zeros over the 8 bytes we just copied -- clearing the buffer
for the next frame costs almost nothing
Note that in VBlank (mode 1) STAT bit 1 is 0, so step 2 waits out all
of VBlank: this routine only uses the 144 visible lines per frame.
*/
void tb_blit(void) __naked {
__asm
ld (_tb_saved_sp), sp
ld hl, #0x8840 ; VRAM destination (tile $84)
ld de, #504 ; 4032 / 8 lines
di
ld sp, #_tb_buf
1$:
pop bc ; bytes 0,1
2$:
ldh a, (0x41) ; STAT
and a, #0x02
jr z, 2$ ; wait for mode 2/3
3$:
ldh a, (0x41)
and a, #0x02
jr nz, 3$ ; wait for mode 0 (HBlank)
ld a, c
ld (hl+), a
ld a, b
ld (hl+), a
pop bc ; bytes 2,3
ld a, c
ld (hl+), a
ld a, b
ld (hl+), a
pop bc ; bytes 4,5
ld a, c
ld (hl+), a
ld a, b
ld (hl+), a
pop bc ; bytes 6,7
ld a, c
ld (hl+), a
ld a, b
ld (hl+), a
; swap SP to the ISR stack, let interrupts in
ld c, l
ld b, h ; BC = VRAM pointer
ldhl sp, #0 ; HL = buffer pointer
ld sp, #_tb_isr_stack+96
ei
4$:
ldh a, (0x41)
and a, #0x02
jr z, 4$ ; wait until HBlank is over
di
ld sp, hl
ld l, c
ld h, b ; HL = VRAM pointer again
ld bc, #0
push bc ; clear the 8 bytes we just popped
push bc
push bc
push bc
add sp, #8
dec de
ld a, d
or a, e
jr nz, 1$
; restore the C stack
ld a, (_tb_saved_sp)
ld l, a
ld a, (_tb_saved_sp+1)
ld h, a
ld sp, hl
ei
ret
__endasm;
}
// ---------------------------------------------------------------------
// drawing
//
// Each pixel row of a tile is two bytes: the low bit-plane, then the high
// bit-plane. Color c (0..3) sets bit (c & 1) in the first and (c & 2) in
// the second. So one pixel = two read-modify-writes, but eight pixels of
// the same color = two plain writes. Fast code draws bytes, not pixels.
uint8_t tb_color = 3;
void tb_pset(uint8_t x, uint8_t y) {
uint8_t* p = tb_rowaddr[y] + ((x & 0xf8) << 1);
uint8_t m = tb_bit[x & 7];
if (tb_color & 1) p[0] |= m; else p[0] &= ~m;
if (tb_color & 2) p[1] |= m; else p[1] &= ~m;
}
// ---------------------------------------------------------------------
// shades
//
// Shade 0,2,4,6 are the solid colors 0..3. Odd shades are a checkerboard
// of the two neighbors. Each shade is described by its two bit-plane
// bytes on even rows and on odd rows.
typedef struct { uint8_t lo0, hi0, lo1, hi1; } Shade;
const Shade shades[7] = {
// lo0 hi0 lo1 hi1
{ 0x00, 0x00, 0x00, 0x00 }, // color 0
{ 0xAA, 0x00, 0x55, 0x00 }, // 0/1 checker
{ 0xFF, 0x00, 0xFF, 0x00 }, // color 1
{ 0x55, 0xAA, 0xAA, 0x55 }, // 1/2 checker
{ 0x00, 0xFF, 0x00, 0xFF }, // color 2
{ 0xAA, 0xFF, 0x55, 0xFF }, // 2/3 checker
{ 0xFF, 0xFF, 0xFF, 0xFF }, // color 3
};
// left mask for x&7, right mask for x&7
const uint8_t lmask[8] = { 0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01 };
const uint8_t rmask[8] = { 0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF };
// Fill pixels xl..xr (inclusive) of row y with a shade.
void span(uint8_t y, uint8_t xl, uint8_t xr, const Shade* s) {
uint8_t *p = tb_rowaddr[y] + ((xl & 0xf8) << 1);
uint8_t lo = (y & 1) ? s->lo1 : s->lo0;
uint8_t hi = (y & 1) ? s->hi1 : s->hi0;
uint8_t m = lmask[xl & 7];
uint8_t n = (xr >> 3) - (xl >> 3); // number of byte boundaries crossed
if (n == 0) {
m &= rmask[xr & 7]; // span starts and ends in one byte
} else {
// left partial byte
p[0] = (p[0] & ~m) | (lo & m);
p[1] = (p[1] & ~m) | (hi & m);
p += 16;
// whole bytes: no masking, two writes per 8 pixels
while (--n) {
p[0] = lo;
p[1] = hi;
p += 16;
}
m = rmask[xr & 7];
}
// right (or only) partial byte
p[0] = (p[0] & ~m) | (lo & m);
p[1] = (p[1] & ~m) | (hi & m);
}
#endif
+94
View File
@@ -0,0 +1,94 @@
/*
Wireframe 3D in a tile framebuffer, in the style of X (Argonaut, 1992).
A cube spins in the 144x112 viewport from tilebuf.h. Each frame:
1. rotate the 8 corners around two axes -- 8-bit fixed point, using a
quarter-square table because the SM83 has no multiply instruction
2. project them with a reciprocal table instead of dividing by Z
3. draw the 12 edges with Bresenham's line algorithm, stepping a VRAM-style
pointer and bit mask instead of recomputing each pixel's address
4. tb_blit() copies the finished picture to VRAM (and clears the buffer)
*/
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
#include "tilebuf.h"
#include "gb3d.h"
// Bresenham's line, walking a framebuffer pointer and a bit mask.
// Moving right one pixel = shift the mask; every 8 pixels, step 16 bytes
// to the next tile. Moving down one pixel = 2 bytes, except every 8 rows,
// where we jump to the next row of tiles.
void line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
uint8_t *p;
uint8_t mask, dx, dy, n, y;
int16_t err;
int8_t xdir;
if (y0 > y1) { // always draw downward
n = x0; x0 = x1; x1 = n;
n = y0; y0 = y1; y1 = n;
}
dy = y1 - y0;
if (x1 >= x0) { dx = x1 - x0; xdir = 1; }
else { dx = x0 - x1; xdir = -1; }
p = tb_rowaddr[y0] + ((x0 & 0xf8) << 1);
mask = tb_bit[x0 & 7];
y = y0;
if (dx >= dy) { // mostly horizontal
err = dx >> 1;
for (n = dx + 1; n; n--) {
p[0] |= mask; p[1] |= mask; // color 3
if (xdir > 0) {
mask >>= 1;
if (!mask) { mask = 0x80; p += 16; }
} else {
mask <<= 1;
if (!mask) { mask = 0x01; p -= 16; }
}
err -= dy;
if (err < 0) {
err += dx;
p += ((++y & 7) == 0) ? (TB_ROWBYTES - 14) : 2;
}
}
} else { // mostly vertical
err = dy >> 1;
for (n = dy + 1; n; n--) {
p[0] |= mask; p[1] |= mask;
p += ((++y & 7) == 0) ? (TB_ROWBYTES - 14) : 2;
err -= dx;
if (err < 0) {
err += dy;
if (xdir > 0) {
mask >>= 1;
if (!mask) { mask = 0x80; p += 16; }
} else {
mask <<= 1;
if (!mask) { mask = 0x01; p -= 16; }
}
}
}
}
}
void main(void) {
uint8_t ay = 0, ax = 0, i;
DISPLAY_OFF;
BGP_REG = 0xE4;
gb3d_init();
tb_init();
DISPLAY_ON;
while (1) {
transform(ay, ax);
for (i = 0; i < 12; i++) {
uint8_t a = edges[i][0], b = edges[i][1];
line(sx[a], sy[a], sx[b], sy[b]);
}
tb_blit(); // ~3.3 frames: copy to VRAM and clear the buffer
ay += 3;
ax += 2;
}
}
+12 -2
View File
@@ -16,19 +16,29 @@ const GB_PRESETS: Preset[] = [
{ id: 'large_map.c', name: 'Large Scrolling Map' },
{ id: 'window.c', name: 'Window Status Bar' },
{ id: 'galaxy.c', name: 'Window + Sprites' },
{ id: 'raster.c', name: 'STAT/LYC Raster Effects' },
{ id: 'interrupts.c', name: 'VBlank + Timer Interrupts' },
{ id: 'vram.c', name: 'VRAM/OAM Access' },
{ id: 'detect.c', name: 'Detect Hardware' },
{ id: 'gbc.c', name: 'Game Boy Color' },
{ id: 'gbdecompress.c', name: 'Decompression' },
{ id: 'music.c', name: 'Music Player' },
{ id: 'aputest.c', name: 'Sound Tester' },
{ id: 'clock.c', name: 'MBC3 Real-Time Clock' },
{ id: 'savegame.c', name: 'Battery Save RAM' },
{ id: 'banking.c', name: 'ROM Bank Switching' },
{ id: 'flicker.c', name: 'Flicker Transparency', category: 'Advanced Graphics' },
{ id: 'parallax.c', name: 'Parallax Scrolling' },
{ id: 'animstream.c', name: 'Streaming Animation' },
{ id: 'raster.c', name: 'STAT/LYC Raster Effects' },
{ id: 'wireframe.c', name: 'Wireframe 3D' },
{ id: 'polyfill.c', name: 'Filled Polygons' },
{ id: 'raycast.c', name: 'Raycasting 3D' },
{ id: 'siegegame.c', name: 'Siege Game', category: 'Games' },
{ id: 'climber.c', name: 'Climber Game' },
{ id: 'chase.c', name: "Shiru's Chase Game" },
{ id: 'pakupaku.c', name: 'Paku Paku' },
{ id: 'hello.sgb', name: 'Hello World (ASM)', category: 'Assembler' },
{ id: 'sm83.c', name: 'C vs. Assembly', category: 'Assembler' },
{ id: 'hello.sgb', name: 'Hello World (ASM)' },
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
];