--- /dev/null
+*.o
+*.d
+*.swp
+*.elf
+*.gba
+*.png
+*.raw
+*.pal
+pngdump
--- /dev/null
+src = $(wildcard src/*.c)
+ssrc = $(wildcard src/*.s)
+#dataobj = data/data.o
+obj = $(src:.c=.o) $(ssrc:.s=.o) $(dataobj)
+dep = $(src:.c=.d)
+name = blender
+elf = $(name).elf
+bin = $(name).gba
+
+ARCH = arm-none-eabi-
+
+CPP = $(ARCH)cpp
+CC = $(ARCH)gcc
+AS = $(ARCH)as
+OBJCOPY = $(ARCH)objcopy
+EMU = vbam
+
+opt = -O1 -fomit-frame-pointer -mcpu=arm7tdmi -mtune=arm7tdmi -mthumb -mthumb-interwork
+#dbg = -g
+
+CFLAGS = $(opt) $(dbg) -pedantic -Wall -MMD
+ASFLAGS = -mthumb-interwork
+LDFLAGS = -mthumb -mthumb-interwork -Wl,-print-gc-sections
+EMUFLAGS = -T 100 -f 1 --agb-print
+
+
+.PHONY: all
+all: $(bin) $(bin_mb)
+
+$(bin): $(elf)
+ $(OBJCOPY) -O binary $(elf) $(bin)
+ gbafix $(bin)
+
+$(elf): $(obj)
+ $(CC) -o $(elf) $(obj) -specs=gba.specs $(LDFLAGS)
+
+-include $(dep)
+
+src/data.o: src/data.s data/bg.raw data/bg.pal
+
+tools/pngdump/pngdump:
+ $(MAKE) -C tools/pngdump
+
+%.raw: %.png tools/pngdump/pngdump
+ tools/pngdump/pngdump -o $@ $<
+
+%.pal: %.png tools/pngdump/pngdump
+ tools/pngdump/pngdump -o $@ -c $<
+
+.PHONY: clean
+clean:
+ rm -f $(obj) $(bin) $(bin_mb) $(elf) $(elf_mb)
+
+.PHONY: cleandep
+cleandep:
+ rm -f $(dep)
+
+.PHONY: install
+install: $(bin)
+ if2a -n -f -W $<
+
+.PHONY: run
+run: $(bin_mb)
+ if2a -m $<
+
+.PHONY: simrun
+simrun: $(bin)
+ $(EMU) $(EMUFLAGS) $(bin)
+
+.PHONY: disasm
+disasm: $(elf)
+ $(ARCH)objdump -d $< >$@
--- /dev/null
+ .section .rodata
+
+ .globl bgimg_pixels
+ .globl bgimg_cmap
+
+bgimg_pixels:
+ .incbin "data/bg.raw"
+bgimg_cmap:
+ .incbin "data/bg.pal"
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef GBAREGS_H_
+#define GBAREGS_H_
+
+#include <stdint.h>
+
+#define VRAM_START_ADDR 0x6000000
+#define VRAM_BG_ADDR VRAM_START_ADDR
+#define VRAM_OBJ_ADDR 0x6010000
+#define VRAM_LFB_OBJ_ADDR 0x6014000
+#define VRAM_LFB_FB0_ADDR VRAM_START_ADDR
+#define VRAM_LFB_FB1_ADDR 0x600a000
+
+/* address of character data block x (4 possible blocks, 16k each) */
+#define VRAM_CHR_BLOCK_ADDR(x) (VRAM_START_ADDR + ((x) << 14))
+/* address of screen data block x (32 possible blocks, 2k each) */
+#define VRAM_SCR_BLOCK_ADDR(x) (VRAM_START_ADDR + ((x) << 11))
+
+/* fields of a background tile in screen memory */
+#define BGTILE_HFLIP 0x0400
+#define BGTILE_VFLIP 0x0800
+#define BGTILE_PAL(x) ((uint16_t)(x) << 12)
+
+/* color palette ram addresses for backgrounds and sprites */
+#define CRAM_BG_ADDR 0x5000000
+#define CRAM_OBJ_ADDR 0x5000200
+
+/* interrupt handler */
+#define INTR_VECTOR (*(volatile uint32_t*)0x3007ffc)
+
+/* battery backed RAM address */
+#define SRAM_ADDR 0xe000000
+
+/* I/O space */
+
+#define REG_BASE 0x4000000
+#define REG8(x) (*(volatile uint8_t*)(REG_BASE + (x)))
+#define REG16(x) (*(volatile uint16_t*)(REG_BASE + (x)))
+#define REG32(x) (*(volatile uint32_t*)(REG_BASE + (x)))
+
+/* ---- display registers ---- */
+#define REG_DISPCNT REG16(0x00)
+#define REG_GREENSWAP REG16(0x02)
+#define REG_DISPSTAT REG16(0x04)
+#define REG_VCOUNT REG16(0x06)
+#define REG_BG0CNT REG16(0x08)
+#define REG_BG1CNT REG16(0x0a)
+#define REG_BG2CNT REG16(0x0c)
+#define REG_BG3CNT REG16(0x0e)
+/* scrolling registers */
+#define REG_BG0HOFS REG16(0x10)
+#define REG_BG0VOFS REG16(0x12)
+#define REG_BG1HOFS REG16(0x14)
+#define REG_BG1VOFS REG16(0x16)
+#define REG_BG2HOFS REG16(0x18)
+#define REG_BG2VOFS REG16(0x1a)
+#define REG_BG3HOFS REG16(0x1c)
+#define REG_BG3VOFS REG16(0x1e)
+/* BG rotation and scaling registers */
+#define REG_BG2PA REG16(0x20)
+#define REG_BG2PB REG16(0x22)
+#define REG_BG2PC REG16(0x24)
+#define REG_BG2PD REG16(0x26)
+#define REG_BG2X REG32(0x28)
+#define REG_BG2Y REG32(0x2c)
+#define REG_BG3PA REG16(0x30)
+#define REG_BG3PB REG16(0x32)
+#define REG_BG3PC REG16(0x34)
+#define REG_BG3PD REG16(0x36)
+#define REG_BG3X REG32(0x38)
+#define REG_BG3Y REG32(0x3c)
+/* window registers */
+#define REG_WIN0H REG16(0x40)
+#define REG_WIN1H REG16(0x42)
+#define REG_WIN0V REG16(0x44)
+#define REG_WIN1V REG16(0x46)
+#define REG_WININ REG16(0x48)
+#define REG_WINOUT REG16(0x4a)
+
+#define REG_MOSAIC REG16(0x4c)
+/* color effects */
+#define REG_BLDCNT REG16(0x50)
+#define REG_BLDALPHA REG16(0x52)
+#define REG_BLDY REG16(0x54)
+
+/* ---- sound registers ---- */
+#define REG_SOUND1CNT_L REG16(0x60)
+#define REG_SOUND1CNT_H REG16(0x62)
+#define REG_SOUND1CNT_X REG16(0x64)
+#define REG_SOUND2CNT_L REG16(0x68)
+#define REG_SOUND2CNT_H REG16(0x6c)
+#define REG_SOUND3CNT_L REG16(0x70)
+#define REG_SOUND3CNT_H REG16(0x72)
+#define REG_SOUND3CNT_X REG16(0x74)
+#define REG_SOUND4CNT_L REG16(0x78)
+#define REG_SOUND4CNT_H REG16(0x7c)
+#define REG_SOUNDCNT_L REG16(0x80)
+#define REG_SOUNDCNT_H REG16(0x82)
+#define REG_SOUNDCNT_X REG16(0x84)
+#define REG_SOUNDBIAS REG16(0x88)
+#define WAVE_RAM_PTR ((unsigned char*)(REG_BASE + 0x90))
+#define REG_FIFO_A REG32(0xa0)
+#define REG_FIFO_B REG32(0xa4)
+#define FIFO_A_PTR ((unsigned char*)(REG_BASE + 0xa0))
+#define FIFO_B_PTR ((unsigned char*)(REG_BASE + 0xa4))
+
+/* ---- DMA registers ---- */
+#define REG_DMA0SAD REG32(0xb0)
+#define REG_DMA0DAD REG32(0xb4)
+#define REG_DMA0CNT_L REG16(0xb8)
+#define REG_DMA0CNT_H REG16(0xba)
+#define REG_DMA1SAD REG32(0xbc)
+#define REG_DMA1DAD REG32(0xc0)
+#define REG_DMA1CNT_L REG16(0xc4)
+#define REG_DMA1CNT_H REG16(0xc6)
+#define REG_DMA2SAD REG32(0xc8)
+#define REG_DMA2DAD REG32(0xcc)
+#define REG_DMA2CNT_L REG16(0xd0)
+#define REG_DMA2CNT_H REG16(0xd2)
+#define REG_DMA3SAD REG32(0xd4)
+#define REG_DMA3DAD REG32(0xd8)
+#define REG_DMA3CNT_L REG16(0xdc)
+#define REG_DMA3CNT_H REG16(0xde)
+
+/* ---- timer registers ---- */
+#define REG_TM0CNT_L REG16(0x100)
+#define REG_TM0CNT_H REG16(0x102)
+#define REG_TM1CNT_L REG16(0x104)
+#define REG_TM1CNT_H REG16(0x106)
+#define REG_TM2CNT_L REG16(0x108)
+#define REG_TM2CNT_H REG16(0x10a)
+#define REG_TM3CNT_L REG16(0x10c)
+#define REG_TM3CNT_H REG16(0x10e)
+
+#define REG_TMCNT_L(x) REG16(0x100 + ((x) << 2))
+#define REG_TMCNT_H(x) REG16(0x102 + ((x) << 2))
+
+/* ---- communication registers (serial/joybus/gpio) ---- */
+#define REG_SIODATA32 REG32(0x120)
+#define REG_SIOMULTI0 REG16(0x120)
+#define REG_SIOMULTI1 REG16(0x122)
+#define REG_SIOMULTI2 REG16(0x124)
+#define REG_SIOMULTI3 REG16(0x126)
+#define REG_SIOCNT REG16(0x128)
+#define REG_SIOMLT_SEND REG16(0x12a)
+#define REG_SIODATA8 REG16(0x12a)
+#define REG_RCNT REG16(0x134)
+#define REG_JOYCNT REG16(0x140)
+#define REG_JOY_RECV REG32(0x150)
+#define REG_JOY_TRANS REG32(0x154)
+#define REG_JOYSTAT REG16(0x158)
+
+/* ---- keypad registers ---- */
+#define REG_KEYINPUT REG16(0x130)
+#define REG_KEYCNT REG16(0x132)
+
+/* ---- interrupts ---- */
+#define REG_IE REG16(0x200)
+#define REG_IF REG16(0x202)
+#define REG_WAITCNT REG16(0x204)
+#define REG_IME REG16(0x208)
+
+#define REG_POSTFLG REG8(0x300)
+#define REG_HALTCNT REG8(0x301)
+#define REG_INTMEMCNT REG32(0x800)
+
+/* REG_DISPSTAT bits */
+#define DISPSTAT_VBLANK 0x01
+#define DISPSTAT_HBLANK 0x02
+#define DISPSTAT_VMATCH 0x04
+#define DISPSTAT_IEN_VBLANK 0x08
+#define DISPSTAT_IEN_HBLANK 0x10
+#define DISPSTAT_IEN_VMATCH 0x20
+#define DISPSTAT_VCOUNT(x) ((uint16_t)(x) << 8)
+
+/* REG_DISPCNT bits */
+#define DISPCNT_MODE(x) (x)
+#define DISPCNT_FB1 0x0010
+#define DISPCNT_HBLANK_OBJPROC 0x0020
+#define DISPCNT_OBJMAP_1D 0x0040
+#define DISPCNT_FORCE_BLANK 0x0080
+#define DISPCNT_BG0 0x0100
+#define DISPCNT_BG1 0x0200
+#define DISPCNT_BG2 0x0400
+#define DISPCNT_BG3 0x0800
+#define DISPCNT_OBJ 0x1000
+#define DISPCNT_WIN0 0x2000
+#define DISPCNT_WIN1 0x4000
+#define DISPCNT_WINOBJ 0x8000
+
+/* REG_BGXCNT bits */
+#define BGCNT_PRIO(x) ((uint16_t)(x))
+#define BGCNT_CHR_BASE(x) ((uint16_t)(x) << 2)
+#define BGCNT_MOSAIC 0x0040
+#define BGCNT_256COL 0x0080
+#define BGCNT_SCR_BASE(x) ((uint16_t)(x) << 8)
+#define BGCNT_WRAP 0x2000
+
+#define BGCNT_SZ(x) ((uint16_t)(x) << 14)
+#define BGCNT_SZ_TX_256X256 BGCNT_SZ(0)
+#define BGCNT_SZ_RS_128X128 BGCNT_SZ(0)
+#define BGCNT_SZ_TX_512X256 BGCNT_SZ(1)
+#define BGCNT_SZ_RS_256X256 BGCNT_SZ(1)
+#define BGCNT_SZ_TX_256X512 BGCNT_SZ(2)
+#define BGCNT_SZ_RS_512X512 BGCNT_SZ(2)
+#define BGCNT_SZ_TX_512X512 BGCNT_SZ(3)
+#define BGCNT_SZ_RS_1024X1024 BGCNT_SZ(3)
+
+/* REG_IF bits */
+#define IF_VBLANK 0x0001
+#define IF_HBLANK 0x0002
+#define IF_VMATCH 0x0004
+#define IF_TIMER0 0x0008
+#define IF_TIMER1 0x0010
+#define IF_TIMER2 0x0020
+#define IF_TIMER3 0x0040
+#define IF_COMM 0x0080
+#define IF_DMA0 0x0100
+#define IF_DMA1 0x0200
+#define IF_DMA2 0x0400
+#define IF_DMA3 0x0800
+#define IF_KEY 0x1000
+#define IF_GPAK 0x2000
+
+/* REG_TMXCNT bits */
+#define TMCNT_PRESCL_CLK1 0
+#define TMCNT_PRESCL_CLK64 1
+#define TMCNT_PRESCL_CLK256 2
+#define TMCNT_PRESCL_CLK1024 3
+
+#define TMCNT_CASCADE 0x04
+#define TMCNT_IE 0x40
+#define TMCNT_EN 0x80
+
+/* REG_KEY* bits */
+#define KEY_A 0x0001
+#define KEY_B 0x0002
+#define KEY_SELECT 0x0004
+#define KEY_START 0x0008
+#define KEY_RIGHT 0x0010
+#define KEY_LEFT 0x0020
+#define KEY_UP 0x0040
+#define KEY_DOWN 0x0080
+#define KEY_RT 0x0100
+#define KEY_LT 0x0200
+
+#define KEYCNT_IE 0x4000
+#define KEYCNT_IAND 0x8000
+
+/* REG_SOUNDCNT_L bits */
+#define SCNT_SS_LVOL(x) ((x) & 7)
+#define SCNT_SS_RVOL(x) (((x) & 7) << 4)
+#define SCNT_SS_VOL(x) (SCNT_SS_LVOL(x) | SCNT_SS_RVOL(x))
+#define SCNT_SS1_EN_R 0x0100
+#define SCNT_SS2_EN_R 0x0200
+#define SCNT_SS3_EN_R 0x0400
+#define SCNT_SS4_EN_R 0x0800
+#define SCNT_SS_EN_R(x) (SCNT_SS1_EN_R << (x))
+#define SCNT_SS1_EN_L 0x1000
+#define SCNT_SS2_EN_L 0x2000
+#define SCNT_SS3_EN_L 0x4000
+#define SCNT_SS4_EN_L 0x8000
+#define SCNT_SS_EN_L(x) (SCNT_SS1_EN_L << (x))
+#define SCNT_SS1_EN (SCNT_SS1_EN_R | SCNT_SS1_EN_L)
+#define SCNT_SS2_EN (SCNT_SS2_EN_R | SCNT_SS2_EN_L)
+#define SCNT_SS3_EN (SCNT_SS3_EN_R | SCNT_SS3_EN_L)
+#define SCNT_SS4_EN (SCNT_SS4_EN_R | SCNT_SS4_EN_L)
+#define SCNT_SS_EN(x) (SCNT_SS_EN_L(x) | SCNT_SS_EN_R(x))
+
+#define SCNT_SS1 0
+#define SCNT_SS2 1
+#define SCNT_SS3 2
+#define SCNT_SS4 3
+
+/* REG_SOUNDCNT_X bits */
+#define SCNT_MASTER_EN 0x0080
+
+/* REG_SOUNDCNT_H bits */
+#define SCNT_SS_VOL_QRT 0x0000
+#define SCNT_SS_VOL_HALF 0x0001
+#define SCNT_SS_VOL_FULL 0x0002
+#define SCNT_DSA_VOL_HALF 0
+#define SCNT_DSA_VOL_FULL 0x0004
+#define SCNT_DSB_VOL_HALF 0
+#define SCNT_DSB_VOL_FULL 0x0008
+#define SCNT_DSA_EN_R 0x0100
+#define SCNT_DSA_EN_L 0x0200
+#define SCNT_DSA_TIMER0 0
+#define SCNT_DSA_TIMER1 0x0400
+#define SCNT_DSA_CLRFIFO 0x0800
+#define SCNT_DSB_EN_R 0x1000
+#define SCNT_DSB_EN_L 0x2000
+#define SCNT_DSB_TIMER0 0
+#define SCNT_DSB_TIMER1 0x4000
+#define SCNT_DSB_CLRFIFO 0x8000
+
+/* REG_DMAxCNT_H bits */
+#define DMACNT_DST_INC 0
+#define DMACNT_DST_DEC 0x0020
+#define DMACNT_DST_FIXED 0x0040
+#define DMACNT_SRC_INC 0
+#define DMACNT_SRC_DEC 0x0080
+#define DMACNT_SRC_FIXED 0x0100
+#define DMACNT_REPEAT 0x0200
+#define DMACNT_16BIT 0
+#define DMACNT_32BIT 0x0400
+#define DMACNT_VBLANK 0x1000
+#define DMACNT_HBLANK 0x2000
+#define DMACNT_SOUND 0x3000
+#define DMACNT_IEN 0x4000
+#define DMACNT_EN 0x8000
+
+
+#endif /* GBAREGS_H_ */
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef GFX_H_
+#define GFX_H_
+
+#include "gbaregs.h"
+
+#define wait_vblank() while(REG_VCOUNT < 160)
+
+#define swap_buffers() \
+ do { \
+ static int vis; \
+ vis ^= 0x10; \
+ REG_DISPCNT = DISPCNT_BG2 | 4 | vis; \
+ } while(0)
+
+#define set_bg_color(idx, r, g, b) \
+ do { \
+ ((uint16_t*)CRAM_BG_ADDR)[idx] = (uint16_t)(r) | ((uint16_t)(g) << 5) | ((uint16_t)(b) << 10); \
+ } while(0)
+
+#endif /* GFX_H_ */
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "intr.h"
+
+#define MAX_INTR 14
+static void (*intr_table[MAX_INTR])(void);
+
+__attribute__ ((target("arm")))
+static void intr_handler(void)
+{
+ int i;
+ uint16_t iflags;
+
+ clr_intr();
+ iflags = REG_IF & 0x3fff;
+
+
+ for(i=0; i<MAX_INTR; i++) {
+ if((iflags & (1 << i)) && intr_table[i]) {
+ intr_table[i]();
+ }
+ }
+
+ REG_IF = iflags; /* ack intr */
+ set_intr();
+}
+
+void intr_init(void)
+{
+ INTR_VECTOR = (uint32_t)intr_handler;
+}
+
+void interrupt(int intr, void (*handler)(void))
+{
+ intr_table[intr] = handler;
+}
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef _INTR_H_
+#define _INTR_H_
+
+#include "gbaregs.h"
+
+/* interrupts */
+enum {
+ INTR_VBLANK,
+ INTR_HBLANK,
+ INTR_VCOUNT,
+ INTR_TIMER0,
+ INTR_TIMER1,
+ INTR_TIMER2,
+ INTR_TIMER3,
+ INTR_COMM,
+ INTR_DMA0,
+ INTR_DMA1,
+ INTR_DMA2,
+ INTR_DMA3,
+ INTR_KEY,
+ INTR_GPAK
+};
+
+void intr_init(void);
+
+/* set/clear interrupts */
+#define set_intr() \
+ do { REG_IME |= 0x0001; } while(0)
+#define clr_intr() \
+ do { REG_IME &= 0xfffe; } while(0)
+
+/* set an interrupt handler */
+void interrupt(int intr, void (*handler)(void));
+
+/* mask/unmask an interrupt */
+#define mask(intr) do {REG_IE &= ~(1 << (intr));} while(0)
+#define unmask(intr) do {REG_IE |= 1 << (intr);} while(0)
+
+#endif /* _INTR_H_ */
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "keyb.h"
+#include "gbaregs.h"
+#include "timer.h"
+
+#define NUM_KEYS 10
+
+static int rep_start, rep_rep;
+static unsigned long first_press[16], last_press[16];
+static uint16_t repmask;
+
+void key_repeat(int start, int rep, uint16_t mask)
+{
+ rep_start = start;
+ rep_rep = rep;
+ repmask = mask;
+}
+
+void update_keyb(void)
+{
+ static uint16_t prevstate;
+ int i;
+ unsigned long msec = timer_msec;
+
+ keystate = ~REG_KEYINPUT;
+ keydelta = keystate ^ prevstate;
+ prevstate = keystate;
+
+ for(i=0; i<NUM_KEYS; i++) {
+ uint16_t bit = 1 << i;
+ if(!(bit & repmask)) {
+ continue;
+ }
+
+ if(keystate & bit) {
+ if(keydelta & bit) {
+ first_press[i] = msec;
+ } else {
+ if(msec - first_press[i] >= rep_start && msec - last_press[i] >= rep_rep) {
+ keydelta |= bit;
+ last_press[i] = msec;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef KEYB_H_
+#define KEYB_H_
+
+#include <stdint.h>
+
+#define KEYPRESS(key) ((keystate & (key)) && (keydelta & (key)))
+#define KEYRELEASE(key) ((keystate & (key)) == 0 && (keydelta & (key)))
+
+uint16_t keystate, keydelta;
+
+void key_repeat(int start, int rep, uint16_t mask);
+
+void update_keyb(void);
+
+#endif /* KEYB_H_ */
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include "gbaregs.h"
+#include "timer.h"
+#include "keyb.h"
+#include "intr.h"
+#include "gfx.h"
+
+static void handle_keys(void);
+
+extern struct { unsigned char r, g, b; } bgimg_cmap[];
+extern unsigned char bgimg_pixels[];
+
+int main(void)
+{
+ int i;
+ uint16_t *cptr;
+ unsigned char r, g, b;
+
+ intr_init();
+ reset_msec_timer();
+ set_intr();
+
+ /* mode 4: 240x160 8bpp */
+ REG_DISPCNT = DISPCNT_BG2 | 4;
+
+ cptr = (uint16_t*)CRAM_BG_ADDR;
+ for(i=0; i<128; i++) {
+ r = bgimg_cmap[i].r >> 3;
+ g = bgimg_cmap[i].g >> 3;
+ b = bgimg_cmap[i].b >> 3;
+ *cptr++ = r | (g << 5) | (b << 10);
+ }
+ memcpy((void*)VRAM_LFB_FB0_ADDR, bgimg_pixels, 240 * 160);
+ memcpy((void*)VRAM_LFB_FB1_ADDR, bgimg_pixels, 240 * 160);
+
+ /*key_repeat(500, 75, KEY_LEFT | KEY_RIGHT | KEY_DOWN | KEY_UP);*/
+
+ for(;;) {
+ wait_vblank();
+ swap_buffers();
+ }
+
+ return 0;
+}
+
+static void handle_keys(void)
+{
+ update_keyb();
+
+ if(KEYPRESS(KEY_UP)) {
+ }
+ if(KEYPRESS(KEY_DOWN)) {
+ }
+ if(KEYPRESS(KEY_LEFT)) {
+ }
+ if(KEYPRESS(KEY_RIGHT)) {
+ }
+}
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "intr.h"
+#include "timer.h"
+
+#define F_CLK 16780000
+/* clock is 16.78MHz
+ * - no prescale: 59.595ns
+ * - prescale 64: 3.814us
+ * - prescale 256: 15.256us
+ * - prescale 1024: 61.025us
+ */
+
+static void timer_intr(void);
+
+void init_timer(int tm, unsigned long rate_hz, void (*intr)(void))
+{
+ static const unsigned long clk[] = {F_CLK, F_CLK / 64, F_CLK / 256, F_CLK / 1024};
+ unsigned long count;
+ int pscl = 0;
+
+ do {
+ count = clk[pscl] / rate_hz;
+ } while(count >= 65536 && ++pscl < 4);
+
+ if(pscl >= 4) return; /* impossible rate */
+
+ REG_TMCNT_H(tm) = 0;
+ REG_TMCNT_L(tm) = 65536 - count;
+ if(intr) {
+ interrupt(INTR_TIMER0 + tm, intr);
+ unmask(INTR_TIMER0 + tm);
+ REG_TMCNT_H(tm) = TMCNT_IE;
+ }
+ REG_TMCNT_H(tm) |= TMCNT_EN | pscl;
+}
+
+void reset_msec_timer(void)
+{
+ REG_TM0CNT_H &= ~TMCNT_EN;
+ interrupt(INTR_TIMER0, timer_intr);
+ timer_msec = 0;
+ REG_TM0CNT_L = 65535 - 16779;
+ REG_TM0CNT_H |= TMCNT_IE | TMCNT_EN;
+ unmask(INTR_TIMER0);
+}
+
+void delay(unsigned long ms)
+{
+ unsigned long end = timer_msec + ms;
+ while(timer_msec < end);
+}
+
+static void timer_intr(void)
+{
+ timer_msec++;
+}
--- /dev/null
+/*
+blender for the Gameboy Advance
+Copyright (C) 2021 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef _TIMER_H_
+#define _TIMER_H_
+
+#include "gbaregs.h"
+
+#define enable_timer(x) \
+ do { REG_TMCNT_H(x) |= TMCNT_EN; } while(0)
+
+#define disable_timer(x) \
+ do { REG_TMCNT_H(x) &= ~TMCNT_EN; } while(0)
+
+volatile unsigned long timer_msec;
+
+void init_timer(int tm, unsigned long rate_hz, void (*intr)(void));
+
+void reset_msec_timer(void);
+
+void delay(unsigned long ms);
+
+#ifdef __thumb__
+#define udelay(x) asm volatile ( \
+ "0: sub %0, %0, #1\n\t" \
+ "bne 0b\n\t" \
+ :: "r"(x) : "cc")
+#else
+#define udelay(x) asm volatile ( \
+ "0: subs %0, %0, #1\n\t" \
+ "bne 0b\n\t" \
+ :: "r"(x) : "cc")
+#endif
+
+#endif /* _TIMER_H_ */
--- /dev/null
+LDFLAGS = -lpng -lz
+
+pngdump: main.o image.o
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+clean:
+ $(RM) main.o
+ $(RM) image.o
+ $(RM) pngdump
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <png.h>
+#include "image.h"
+
+int alloc_image(struct image *img, int x, int y, int bpp)
+{
+ memset(img, 0, sizeof *img);
+ img->width = x;
+ img->height = y;
+ img->bpp = bpp;
+ img->scansz = img->pitch = x * bpp / 8;
+
+ if(!(img->pixels = malloc(y * img->scansz))) {
+ fprintf(stderr, "failed to allocate %dx%d (%dbpp) pixel buffer\n", x, y, bpp);
+ return -1;
+ }
+
+ /* just a guess, assume the user will fill the details, but set up reasonable
+ * defaults just in case...
+ */
+ if(bpp <= 8) {
+ img->nchan = 1;
+ img->cmap_ncolors = 1 << bpp;
+ } else if(bpp <= 24) {
+ img->nchan = 3;
+ } else {
+ img->nchan = 4;
+ }
+ return 0;
+}
+
+int load_image(struct image *img, const char *fname)
+{
+ int i;
+ FILE *fp;
+ png_struct *png;
+ png_info *info;
+ int chan_bits, color_type;
+ png_uint_32 xsz, ysz;
+ png_color *palette;
+ unsigned char **scanline;
+ unsigned char *dptr;
+
+ if(!(fp = fopen(fname, "rb"))) {
+ fprintf(stderr, "failed to open: %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+
+ if(!(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+ fclose(fp);
+ return -1;
+ }
+ if(!(info = png_create_info_struct(png))) {
+ fclose(fp);
+ png_destroy_read_struct(&png, 0, 0);
+ return -1;
+ }
+ if(setjmp(png_jmpbuf(png))) {
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return -1;
+ }
+
+ png_init_io(png, fp);
+ png_read_png(png, info, 0, 0);
+
+ png_get_IHDR(png, info, &xsz, &ysz, &chan_bits, &color_type, 0, 0, 0);
+ img->width = xsz;
+ img->height = ysz;
+ img->nchan = png_get_channels(png, info);
+ img->bpp = img->nchan * chan_bits;
+ img->scansz = img->pitch = xsz * img->bpp / 8;
+ img->cmap_ncolors = 0;
+
+ if(color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_get_PLTE(png, info, &palette, &img->cmap_ncolors);
+ memcpy(img->cmap, palette, img->cmap_ncolors * sizeof *img->cmap);
+ }
+
+ if(!(img->pixels = malloc(ysz * img->scansz))) {
+ perror("failed to allocate pixel buffer");
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return -1;
+ }
+ dptr = img->pixels;
+
+ scanline = (unsigned char**)png_get_rows(png, info);
+ for(i=0; i<ysz; i++) {
+ memcpy(dptr, scanline[i], img->scansz);
+ dptr += img->pitch;
+ }
+
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return 0;
+}
+
+int save_image(struct image *img, const char *fname)
+{
+ int i, chan_bits, coltype;
+ FILE *fp;
+ png_struct *png;
+ png_info *info;
+ png_text txt;
+ unsigned char **scanline = 0;
+ unsigned char *pptr;
+
+ if(!(fp = fopen(fname, "wb"))) {
+ fprintf(stderr, "save_image: failed to open: %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+
+ if(!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+ fclose(fp);
+ return -1;
+ }
+ if(!(info = png_create_info_struct(png))) {
+ png_destroy_write_struct(&png, 0);
+ fclose(fp);
+ return -1;
+ }
+
+ txt.compression = PNG_TEXT_COMPRESSION_NONE;
+ txt.key = "Software";
+ txt.text = "img2tiles";
+ txt.text_length = 0;
+
+ if(setjmp(png_jmpbuf(png))) {
+ png_destroy_write_struct(&png, &info);
+ free(scanline);
+ fclose(fp);
+ return -1;
+ }
+
+ switch(img->nchan) {
+ case 1:
+ if(img->cmap_ncolors > 0) {
+ coltype = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ coltype = PNG_COLOR_TYPE_GRAY;
+ }
+ break;
+ case 2:
+ coltype = PNG_COLOR_TYPE_GRAY_ALPHA;
+ break;
+ case 3:
+ coltype = PNG_COLOR_TYPE_RGB;
+ break;
+ case 4:
+ coltype = PNG_COLOR_TYPE_RGB_ALPHA;
+ break;
+ }
+
+ chan_bits = img->bpp / img->nchan;
+ png_set_IHDR(png, info, img->width, img->height, chan_bits, coltype, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_set_text(png, info, &txt, 1);
+
+ if(img->cmap_ncolors > 0) {
+ png_set_PLTE(png, info, (png_color*)img->cmap, img->cmap_ncolors);
+ }
+
+ if(!(scanline = malloc(img->height * sizeof *scanline))) {
+ png_destroy_write_struct(&png, &info);
+ fclose(fp);
+ return -1;
+ }
+
+ pptr = img->pixels;
+ for(i=0; i<img->height; i++) {
+ scanline[i] = pptr;
+ pptr += img->pitch;
+ }
+ png_set_rows(png, info, scanline);
+
+ png_init_io(png, fp);
+ png_write_png(png, info, 0, 0);
+ png_destroy_write_struct(&png, &info);
+ free(scanline);
+ fclose(fp);
+ return 0;
+}
+
+
+int cmp_image(struct image *a, struct image *b)
+{
+ int i;
+ unsigned char *aptr = a->pixels;
+ unsigned char *bptr = b->pixels;
+
+ if(a->width != b->width || a->height != b->height || a->bpp != b->bpp || a->nchan != b->nchan) {
+ return -1;
+ }
+
+ for(i=0; i<a->height; i++) {
+ if(memcmp(aptr, bptr, a->scansz) != 0) {
+ return -1;
+ }
+ aptr += a->pitch;
+ bptr += b->pitch;
+ }
+ return 0;
+}
+
+void blit(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy)
+{
+ int i;
+ unsigned char *sptr, *dptr;
+
+ assert(src->bpp == dst->bpp);
+ assert(src->nchan == dst->nchan);
+
+ if(sx < 0) { w += sx; sx = 0; }
+ if(sy < 0) { h += sy; sy = 0; }
+ if(dx < 0) { w += dx; sx -= dx; dx = 0; }
+ if(dy < 0) { h += dy; sy -= dy; dy = 0; }
+ if(sx + w >= src->width) w = src->width - sx;
+ if(sy + h >= src->height) h = src->height - sy;
+ if(dx + w >= dst->width) w = dst->width - dx;
+ if(dy + h >= dst->height) h = dst->height - dy;
+
+ if(w <= 0 || h <= 0) return;
+
+ sptr = src->pixels + sy * src->pitch + sx * src->bpp / 8;
+ dptr = dst->pixels + dy * dst->pitch + dx * dst->bpp / 8;
+
+ for(i=0; i<h; i++) {
+ memcpy(dptr, sptr, w * dst->bpp / 8);
+ dptr += dst->pitch;
+ sptr += src->pitch;
+ }
+}
--- /dev/null
+#ifndef IMAGE_H_
+#define IMAGE_H_
+
+struct cmapent {
+ unsigned char r, g, b;
+};
+
+struct image {
+ int width, height;
+ int bpp;
+ int nchan;
+ int scansz, pitch;
+ int cmap_ncolors;
+ struct cmapent cmap[256];
+ unsigned char *pixels;
+};
+
+int alloc_image(struct image *img, int x, int y, int bpp);
+int load_image(struct image *img, const char *fname);
+int save_image(struct image *img, const char *fname);
+
+int cmp_image(struct image *a, struct image *b);
+
+void blit(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy);
+
+#endif /* IMAGE_H_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "image.h"
+
+void print_usage(const char *argv0);
+
+int main(int argc, char **argv)
+{
+ int i, mode = 0;
+ int text = 0;
+ char *fname = 0, *outfname = 0;
+ struct image img;
+ FILE *out = stdout;
+
+ for(i=1; i<argc; i++) {
+ if(argv[i][0] == '-') {
+ if(argv[i][2] == 0) {
+ switch(argv[i][1]) {
+ case 'p':
+ mode = 0;
+ break;
+
+ case 'c':
+ mode = 1;
+ break;
+
+ case 'i':
+ mode = 2;
+ break;
+
+ case 't':
+ text = 1;
+ break;
+
+ case 'o':
+ if(!argv[++i]) {
+ fprintf(stderr, "%s must be followed by a filename\n", argv[i - 1]);
+ return 1;
+ }
+ outfname = argv[i];
+ break;
+
+ case 'h':
+ print_usage(argv[0]);
+ return 0;
+
+ default:
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ print_usage(argv[0]);
+ return 1;
+ }
+ } else {
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ print_usage(argv[0]);
+ return 1;
+ }
+ } else {
+ if(fname) {
+ fprintf(stderr, "unexpected argument: %s\n", argv[i]);
+ print_usage(argv[0]);
+ return 1;
+ }
+ fname = argv[i];
+ }
+ }
+
+ if(!fname) {
+ fprintf(stderr, "pass the filename of a PNG file\n");
+ return 1;
+ }
+ if(load_image(&img, fname) == -1) {
+ fprintf(stderr, "failed to load PNG file: %s\n", fname);
+ return 1;
+ }
+
+ if(outfname) {
+ if(!(out = fopen(outfname, "wb"))) {
+ fprintf(stderr, "failed to open output file: %s: %s\n", outfname, strerror(errno));
+ return 1;
+ }
+ }
+
+ switch(mode) {
+ case 0:
+ fwrite(img.pixels, 1, img.scansz * img.height, out);
+ break;
+
+ case 1:
+ if(text) {
+ for(i=0; i<img.cmap_ncolors; i++) {
+ printf("%d %d %d\n", img.cmap[i].r, img.cmap[i].g, img.cmap[i].b);
+ }
+ } else {
+ fwrite(img.cmap, sizeof img.cmap[0], img.cmap_ncolors, out);
+ }
+ break;
+
+ case 2:
+ printf("size: %dx%d\n", img.width, img.height);
+ printf("bit depth: %d\n", img.bpp);
+ printf("scanline size: %d bytes\n", img.scansz);
+ if(img.cmap_ncolors > 0) {
+ printf("colormap entries: %d\n", img.cmap_ncolors);
+ } else {
+ printf("color channels: %d\n", img.nchan);
+ }
+ break;
+ }
+
+ fclose(out);
+ return 0;
+}
+
+void print_usage(const char *argv0)
+{
+ printf("Usage: %s [options] <input file>\n", argv0);
+ printf("Options:\n");
+ printf(" -p: dump pixels (default)\n");
+ printf(" -c: dump colormap (palette) entries\n");
+ printf(" -i: print image information\n");
+ printf(" -t: dump as text\n");
+ printf(" -h: print usage and exit\n");
+}