aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBad Diode <bd@badd10de.dev>2021-06-02 17:26:08 +0200
committerBad Diode <bd@badd10de.dev>2021-06-02 17:26:08 +0200
commitf6686f1e86927f038086023362251ebe78ce5ad6 (patch)
treed196fc1c32c55442a2ac75d4ce046b1c0e0d6d48
downloadstepper-f6686f1e86927f038086023362251ebe78ce5ad6.tar.gz
stepper-f6686f1e86927f038086023362251ebe78ce5ad6.zip
Init repo with basic BG framebuffer renderer
-rw-r--r--.gitignore1
-rw-r--r--Makefile79
-rw-r--r--src/bios_calls.s337
-rw-r--r--src/common.h667
-rw-r--r--src/filesystem.c409
-rw-r--r--src/filesystem.h36
-rw-r--r--src/interrupts.c91
-rw-r--r--src/interrupts.s89
-rw-r--r--src/main.c81
-rw-r--r--src/renderer.c86
-rw-r--r--src/shorthand.h47
11 files changed, 1923 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
build
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1dd6375
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,79 @@
1.POSIX:
2.SUFFIXES:
3
4# Path to the development kit (devkitARM) and the GBA library.
5DEVKITPRO := /opt/devkitpro
6DEVKITBIN := $(DEVKITPRO)/devkitARM/bin
7DEVKITTOOLS := $(DEVKITPRO)/tools/bin
8LIBGBA_DIR := $(DEVKITPRO)/libgba
9LIBGBA_SRC := $(DEVKITPRO)/libgba/include/
10LIBGBA := $(LIBGBA_DIR)/lib/libgba.a
11LIBGBA += $(LIBGBA_DIR)/lib/libfat.a
12LIBGBA += $(LIBGBA_DIR)/lib/libmm.a
13
14# Source code location and files to watch for changes.
15SRC_DIR := src
16BUILD_DIR := build
17SRC_MAIN := $(SRC_DIR)/main.c
18ASM_FILES := $(wildcard $(SRC_DIR)/*.s)
19WATCH_SRC := $(shell find $(SRC_DIR) -name *.c -or -name *.s -or -name *.h)
20INC_DIRS := $(shell find $(SRC_DIR) -type d)
21INC_DIRS += $(BUILD_DIR)
22INC_FLAGS := $(addprefix -I,$(INC_DIRS))
23INC_FLAGS += -I$(LIBGBA_SRC)
24
25# Output library names and executables.
26TARGET := sequencer
27ELF := $(BUILD_DIR)/$(TARGET).elf
28BIN := $(BUILD_DIR)/$(TARGET).gba
29
30# Compiler and linker configuration.
31CC := $(DEVKITBIN)/arm-none-eabi-gcc
32OBJCOPY := $(DEVKITBIN)/arm-none-eabi-objcopy
33ARCH := -mthumb -mthumb-interwork
34SPECS := -specs=gba.specs
35CONFIG :=
36CFLAGS := -Wall -Wextra -pedantic -Wno-incompatible-pointer-types
37CFLAGS += -fno-strict-aliasing
38CFLAGS += -mcpu=arm7tdmi -mtune=arm7tdmi $(ARCH)
39CFLAGS += $(INC_FLAGS)
40CFLAGS += $(CONFIG)
41LDFLAGS := $(ARCH) $(SPECS)
42LDLIBS := $(LIBGBA)
43RELEASE_CFLAGS := -DNDEBUG -O3
44DEBUG_CFLAGS := -DDEBUG -O2 -g
45
46.PHONY: clean run
47
48# Setup debug/release builds.
49# make clean && make <target> DEBUG=0
50# make clean && make <target> DEBUG=1
51DEBUG ?= 0
52ifeq ($(DEBUG), 1)
53 CFLAGS += $(DEBUG_CFLAGS)
54else
55 CFLAGS += $(RELEASE_CFLAGS)
56endif
57
58main: $(BUILD_DIR) $(BIN)
59
60# Strip and fix header to create final .gba file.
61$(BIN): $(ELF)
62 $(OBJCOPY) -v -O binary $(ELF) $(BIN)
63 $(DEVKITTOOLS)/gbafix $(BIN)
64
65# Link files.
66$(ELF): $(SRC_MAIN) $(WATCH_SRC)
67 $(CC) $(CFLAGS) $(LDFLAGS) -o $(ELF) $(SRC_MAIN) $(ASM_FILES) $(LDLIBS)
68
69# Create build directory if needed.
70$(BUILD_DIR):
71 mkdir -p $(BUILD_DIR)
72
73# Test the output .gba in an emulator.
74run: main
75 mgba-qt $(BIN)
76
77# Remove build directory.
78clean:
79 rm -rf $(BUILD_DIR)
diff --git a/src/bios_calls.s b/src/bios_calls.s
new file mode 100644
index 0000000..740fa02
--- /dev/null
+++ b/src/bios_calls.s
@@ -0,0 +1,337 @@
1@
2@ Arithmetic functions.
3@
4
5@ Division.
6 .text
7 .code 16
8 .align 2
9 .global bios_div
10 .thumb_func
11bios_div:
12 swi 0x06
13 bx lr
14
15@ Square root.
16 .text
17 .code 16
18 .align 2
19 .global bios_sqrt
20 .thumb_func
21bios_sqrt:
22 swi 0x08
23 bx lr
24
25@ Arc-tangent.
26 .text
27 .code 16
28 .align 2
29 .global bios_atan
30 .thumb_func
31bios_atan:
32 swi 0x09
33 bx lr
34
35@ Arc-tangent2.
36 .text
37 .code 16
38 .align 2
39 .global bios_atan2
40 .thumb_func
41bios_atan2:
42 swi 0x0a
43 bx lr
44
45@
46@ Rotation/Scaling.
47@
48
49@ BG Affine Set.
50 .text
51 .code 16
52 .align 2
53 .global bios_bg_affine_set
54 .thumb_func
55bios_bg_affine_set:
56 swi 0x0e
57 bx lr
58
59@ OBJ Affine Set.
60 .text
61 .code 16
62 .align 2
63 .global bios_obj_affine_set
64 .thumb_func
65bios_obj_affine_set:
66 swi 0x0f
67 bx lr
68
69@
70@ (De)compression.
71@
72
73@ Bit unpacking.
74 .text
75 .code 16
76 .align 2
77 .global bios_bit_unpack
78 .thumb_func
79bios_bit_unpack:
80 swi 0x10
81 bx lr
82
83@ Huffman decompression.
84 .text
85 .code 16
86 .align 2
87 .global bios_huff_expand
88 .thumb_func
89bios_huff_expand:
90 swi 0x13
91 bx lr
92
93@ LZ77 decompression (Fast, WRAM, 8 bit writes).
94 .text
95 .code 16
96 .align 2
97 .global bios_lz77_expand_wram
98 .thumb_func
99bios_lz77_expand_wram:
100 swi 0x11
101 bx lr
102
103@ LZ77 decompression (Slow, VRAM, 16 bit writes).
104 .text
105 .code 16
106 .align 2
107 .global bios_lz77_expand_vram
108 .thumb_func
109bios_lz77_expand_vram:
110 swi 0x12
111 bx lr
112
113@ Run length encoding decompression (Fast, WRAM, 8 bit writes).
114 .text
115 .code 16
116 .align 2
117 .global bios_rle_expand_wram
118 .thumb_func
119bios_rle_expand_wram:
120 swi 0x14
121 bx lr
122
123@ Run length encoding decompression (Slow, VRAM, 16 bit writes).
124 .text
125 .code 16
126 .align 2
127 .global bios_rle_expand_vram
128 .thumb_func
129bios_rle_expand_vram:
130 swi 0x15
131 bx lr
132
133@
134@ Memory copy.
135@
136
137@ Memcopy/memfill in 32 byte units (Fast).
138 .text
139 .code 16
140 .align 2
141 .global bios_memcopy_32
142 .thumb_func
143bios_memcopy_32:
144 swi 0x0C
145 bx lr
146
147@ Memcopy/memfill in 4 / 2 byte units (Slow).
148 .text
149 .code 16
150 .align 2
151 .global bios_memcopy_8
152 .thumb_func
153bios_memcopy_8:
154 swi 0x0B
155 bx lr
156
157@
158@ Sound functions.
159@
160
161@ MIDI key to frequency.
162 .text
163 .code 16
164 .align 2
165 .global bios_midi2freq
166 .thumb_func
167bios_midi2freq:
168 swi 0x1f
169 bx lr
170
171@ Sound bias.
172 .text
173 .code 16
174 .align 2
175 .global bios_sound_bias
176 .thumb_func
177bios_sound_bias:
178 swi 0x19
179 bx lr
180
181@ Sound channels clear.
182 .text
183 .code 16
184 .align 2
185 .global bios_sound_clear
186 .thumb_func
187bios_sound_clear:
188 swi 0x1e
189 bx lr
190
191@ Sound driver initialization.
192 .text
193 .code 16
194 .align 2
195 .global bios_sound_init
196 .thumb_func
197bios_sound_init:
198 swi 0x1a
199 bx lr
200
201@ Sound main function. To be called each 1/60 of a second after the sound VSync.
202 .text
203 .code 16
204 .align 2
205 .global bios_sound_main
206 .thumb_func
207bios_sound_main:
208 swi 0x1c
209 bx lr
210
211@ Sound driver operation mode.
212 .text
213 .code 16
214 .align 2
215 .global bios_sound_mode
216 .thumb_func
217bios_sound_mode:
218 swi 0x1b
219 bx lr
220
221@ Sound VSync. Called just after the VBlank interrupt each 1/60 of a second.
222 .text
223 .code 16
224 .align 2
225 .global bios_sound_vsync
226 .thumb_func
227bios_sound_vsync:
228 swi 0x1d
229 bx lr
230
231@ Sound VSync off In case of issues manually call this to stop the sound.
232 .text
233 .code 16
234 .align 2
235 .global bios_sound_vsync_off
236 .thumb_func
237bios_sound_vsync_off:
238 swi 0x28
239 bx lr
240
241@
242@ Halt/Reset functions.
243@
244
245@ Halt until interrupt request.
246 .text
247 .code 16
248 .align 2
249 .global bios_halt
250 .thumb_func
251bios_halt:
252 swi 0x02
253 bx lr
254
255@ Halt until one of the specified interrupts occur.
256 .text
257 .code 16
258 .align 2
259 .global bios_interrupt_wait
260 .thumb_func
261bios_interrupt_wait:
262 swi 0x04
263 bx lr
264
265@ Halt until the VBlank interrupt occurs.
266 .text
267 .code 16
268 .align 2
269 .global bios_vblank_wait
270 .thumb_func
271bios_vblank_wait:
272 swi 0x05
273 bx lr
274
275@ Stop. Switches GBA to low power mode.
276 .text
277 .code 16
278 .align 2
279 .global bios_stop
280 .thumb_func
281bios_stop:
282 swi 0x03
283 bx lr
284
285@ Soft reset.
286 .text
287 .code 16
288 .align 2
289 .global bios_soft_reset
290 .thumb_func
291bios_soft_reset:
292 swi 0x00
293 bx lr
294
295@ Register RAM reset.
296 .text
297 .code 16
298 .align 2
299 .global bios_regram_reset
300 .thumb_func
301bios_regram_reset:
302 swi 0x01
303 bx lr
304
305@ Hard reset.
306 .text
307 .code 16
308 .align 2
309 .global bios_hard_reset
310 .thumb_func
311bios_hard_reset:
312 swi 0x26
313 bx lr
314
315@
316@ Misc functions.
317@
318
319@ BIOS checksum.
320 .text
321 .code 16
322 .align 2
323 .global bios_bios_checksum
324 .thumb_func
325bios_bios_checksum:
326 swi 0x0d
327 bx lr
328
329@ MultiBoot.
330 .text
331 .code 16
332 .align 2
333 .global bios_multiboot
334 .thumb_func
335bios_multiboot:
336 swi 0x25
337 bx lr
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..fbe01a4
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,667 @@
1#ifndef COMMON_H
2#define COMMON_H
3
4#include "shorthand.h"
5
6#define CPU_FREQUENCY (2 << 23)
7
8//
9// Memory sections.
10//
11
12// Defines for the different memory sections in the GBA.
13#define MEM_SROM 0x00000000
14#define MEM_EW 0x02000000
15#define MEM_IW 0x03000000
16#define MEM_IO 0x04000000
17#define MEM_PAL 0x05000000
18#define MEM_VRAM 0x06000000
19#define MEM_OAM 0x07000000
20#define MEM_PAK 0x08000000
21#define MEM_CART 0x0E000000
22
23//
24// Display modes.
25//
26
27// Display registers.
28#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000))
29#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004))
30#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006))
31
32// The first three bits in the DISP_CTRL are used to control the video mode.
33#define DISP_MODE_0 0x0000
34#define DISP_MODE_1 0x0001
35#define DISP_MODE_2 0x0002
36#define DISP_MODE_3 0x0003
37#define DISP_MODE_4 0x0004
38#define DISP_MODE_5 0x0005
39#define DISP_GB (1 << 3)
40#define DISP_PAGE (1 << 4)
41#define DISP_OAM_HBLANK (1 << 5)
42#define DISP_OBJ_1D (1 << 6)
43#define DISP_BLANK (1 << 7)
44#define DISP_BG_0 (1 << 8)
45#define DISP_BG_1 (1 << 9)
46#define DISP_BG_2 (1 << 10)
47#define DISP_BG_3 (1 << 11)
48#define DISP_OBJ (1 << 12)
49#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D
50
51// These bits are used to control the DISP_STATUS register.
52#define DISP_VBLANK_STATUS (1 << 0x0)
53#define DISP_HBLANK_STATUS (1 << 0x1)
54#define DISP_VCOUNT_STATUS (1 << 0x2)
55#define DISP_VBLANK_IRQ (1 << 0x3)
56#define DISP_HBLANK_IRQ (1 << 0x4)
57#define DISP_VCOUNT_IRQ (1 << 0x5)
58#define DISP_VCOUNT_TRIGGER(N) ((N) << 0x8)
59
60// Registers to control of BG layers.
61#define BG_CTRL(N) *((vu16*)(0x04000008 + 0x0002 * (N)))
62
63// Bits to control the background.
64#define BG_PRIORITY(N) ((N) & 0x3)
65#define BG_CHARBLOCK(N) ((N) << 2)
66#define BG_MOSAIC (1 << 6)
67#define BG_HIGH_COLOR (1 << 7)
68#define BG_SCREENBLOCK(N) ((N) << 8)
69#define BG_AFFINE (1 << 0xD)
70#define BG_SIZE(N) ((N) << 0xE)
71
72// BG registers for horizontal displacement.
73#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0))
74#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1))
75#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2))
76#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3))
77
78// BG registers for vertical displacement.
79#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0))
80#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1))
81#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2))
82#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3))
83
84// Screen settings.
85#define SCREEN_WIDTH 240
86#define SCREEN_HEIGHT 160
87
88//
89// Colors.
90//
91
92// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component
93// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0).
94typedef u16 Color;
95
96// A palette is composed of 16 colors, with color at index 0 being transparent
97// for sprites.
98typedef Color Palette[16];
99
100// Inline function to calculate the 15 bit color value.
101#define RGB15(R,G,B) (u16)(((B) << 10) | ((G) << 5) | (R));
102
103// Some nice default colors.
104#define COLOR_RED RGB15(31, 0,12)
105#define COLOR_BLUE RGB15(2, 15,30)
106#define COLOR_CYAN RGB15(0, 30,30)
107#define COLOR_GREY RGB15(12,12,12)
108#define COLOR_BLACK RGB15(0, 0, 0)
109#define COLOR_WHITE RGB15(28,28,28)
110
111//
112// Tile memory access.
113//
114
115// NOTE: Only defining 4bpp tiles for now.
116// TODO: typedef u32 Tile[8];
117typedef struct Tile {
118 u32 row[8];
119} Tile;
120
121// Screenblocks and charblocks (tile blocks).
122typedef Tile TileBlock[512];
123#define TILE_MEM ((TileBlock*) MEM_VRAM)
124typedef u16 ScreenBlock[1024];
125#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM)
126
127// Screenblock entry bits.
128#define SCREENBLOCK_ENTRY_H_FLIP (1 << 0xA)
129#define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB)
130#define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC)
131
132inline size_t
133se_index(size_t tile_x, size_t tile_y, size_t map_width) {
134 size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5));
135 return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32);
136}
137
138// We can treat the screen as a HxW matrix. With the following macro we can
139// write a pixel to the screen at the (x, y) position using:
140//
141// FRAMEBUFFER[y][x] = color;
142//
143typedef Color Scanline[SCREEN_WIDTH];
144#define FRAMEBUFFER ((Scanline*) MEM_VRAM)
145#define SCREEN_BUFFER ((u16*) MEM_VRAM)
146#define PAL_BUFFER_BG ((u16*) MEM_PAL)
147#define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200))
148#define PAL_BANK_BG ((Palette*) MEM_PAL)
149#define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200))
150
151//
152// Sprites.
153//
154
155// Using macros instead of aligned structs for setting up OBJ attributes and
156// affine parameters.
157// TODO: Benchmark if this would be slower or the same that TONC's
158// implementation.
159// TODO: Cleanup OBJ/OAM memory copying and access.
160#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N)))
161#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N)))
162#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N)))
163#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N)))
164#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N)))
165#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N)))
166#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N)))
167
168// OBJ_ATTR_0 parameters
169#define OBJ_Y_COORD(N) ((N) & 0xFF)
170#define OBJ_NORMAL (0x00 << 0x8)
171#define OBJ_AFFINE (0x01 << 0x8)
172#define OBJ_HIDDEN (0x02 << 0x8)
173#define OBJ_AFFINE_2X (0x03 << 0x8)
174#define OBJ_ALPHA_BLEND (0x01 << 0xA)
175#define OBJ_WINDOW (0x02 << 0xA)
176#define OBJ_SHAPE_SQUARE (0x00 << 0xE)
177#define OBJ_SHAPE_WIDE (0x01 << 0xE)
178#define OBJ_SHAPE_TALL (0x02 << 0xE)
179
180// OBJ_ATTR_1 parameters
181#define OBJ_X_COORD(N) ((N) & 0x1FF)
182#define OBJ_AFFINE_IDX(N) ((N) << 0x9)
183#define OBJ_H_FLIP (0x01 << 0xC)
184#define OBJ_V_FLIP (0x01 << 0xD)
185#define OBJ_SIZE_SMALL (0x00 << 0xE)
186#define OBJ_SIZE_MID (0x01 << 0xE)
187#define OBJ_SIZE_BIG (0x02 << 0xE)
188#define OBJ_SIZE_HUGE (0x03 << 0xE)
189
190// OBJ_ATTR_2 parameters
191#define OBJ_TILE_INDEX(N) ((N) & 0x3FF)
192#define OBJ_PRIORITY(N) ((N) << 0xA)
193#define OBJ_PAL_BANK(N) ((N) << 0xC)
194
195//
196// Mode 4 page flipping
197//
198
199static inline
200void
201flip_page(vu16 *backbuffer) {
202 backbuffer = (u16*)((u32)backbuffer ^ 0x0A000);
203 DISP_CTRL ^= DISP_PAGE;
204}
205
206#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM)
207#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000))
208
209//
210// Profiling.
211//
212
213#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0))
214#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1))
215#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2))
216#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3))
217#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0))
218#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1))
219#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2))
220#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3))
221
222// Timer control bits.
223#define TIMER_CTRL_FREQ_0 0
224#define TIMER_CTRL_FREQ_1 1
225#define TIMER_CTRL_FREQ_2 2
226#define TIMER_CTRL_FREQ_3 3
227#define TIMER_CTRL_CASCADE (1 << 2)
228#define TIMER_CTRL_IRQ (1 << 6)
229#define TIMER_CTRL_ENABLE (1 << 7)
230#define TIMER_CTRL_DISABLE (0 << 7)
231
232// We use timers 2 and 3 to count the number of cycles since the profile_start
233// functions is called. Don't use if the code we are trying to profile make use
234// of these timers.
235static inline
236void
237profile_start(void) {
238 TIMER_DATA_2 = 0;
239 TIMER_DATA_3 = 0;
240 TIMER_CTRL_2 = 0;
241 TIMER_CTRL_3 = 0;
242 TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE;
243 TIMER_CTRL_2 = TIMER_CTRL_ENABLE;
244}
245
246static inline
247u32
248profile_stop(void) {
249 TIMER_CTRL_2 = 0;
250 return (TIMER_DATA_3 << 16) | TIMER_DATA_2;
251}
252
253static inline
254u32
255profile_measure(void) {
256 return (TIMER_DATA_3 << 16) | TIMER_DATA_2;
257}
258
259//
260// Input handling.
261//
262
263// Memory address for key input and control register
264#define KEY_INPUTS *((vu16*) 0x04000130)
265#define KEY_CTRL *((vu16*) 0x04000132)
266
267// Key control register bits.
268#define KEY_IRQ_KEY(N) (N)
269#define KEY_IRQ (1 << 0xE)
270#define KEY_IRQ_IF_SET (1 << 0xF)
271
272// Alias for key pressing bits.
273#define KEY_A (1 << 0)
274#define KEY_B (1 << 1)
275#define KEY_SELECT (1 << 2)
276#define KEY_START (1 << 3)
277#define KEY_RIGHT (1 << 4)
278#define KEY_LEFT (1 << 5)
279#define KEY_UP (1 << 6)
280#define KEY_DOWN (1 << 7)
281#define KEY_R (1 << 8)
282#define KEY_L (1 << 9)
283
284#define KEY_MASK 0x03FF
285
286// Saving the previous and current key states as globals for now.
287static u16 key_curr = 0;
288static u16 key_prev = 0;
289
290static inline
291void
292poll_keys(void) {
293 key_prev = key_curr;
294 key_curr = ~KEY_INPUTS & KEY_MASK;
295}
296
297// Returns true if the given key has been pressed at time of calling and was not
298// pressed since the previous call. For example, if a key is being held, this
299// function will return `true` only on the frame where the key initially
300// activated.
301static inline
302u32
303key_tap(u32 key) {
304 return (key_curr & key) & ~(key_prev & key);
305}
306
307// Check if a given key is currently pressed.
308static inline
309u32
310key_pressed(u32 key) {
311 return (key_curr & key);
312}
313
314// Check if a given key was just released.
315static inline
316u32
317key_released(u32 key) {
318 return ~(key_curr & key) & (key_prev & key);
319}
320
321// Check if the given key is pressed and has been since at least one frame.
322static inline
323u32
324key_hold(u32 key) {
325 return key_curr & key_prev & key;
326}
327
328// Check if the given key/button is currently pressed.
329#define KEY_PRESSED(key) (~(KEY_INPUTS) & key)
330
331// Back/unpack bits.
332static inline
333u32
334unpack_1bb(u8 hex) {
335 const u32 conversion_u32[16] = {
336 0x00000000, 0x00000001, 0x00000010, 0x00000011,
337 0x00000100, 0x00000101, 0x00000110, 0x00000111,
338 0x00001000, 0x00001001, 0x00001010, 0x00001011,
339 0x00001100, 0x00001101, 0x00001110, 0x00001111,
340 };
341 u8 low = hex & 0xF;
342 u8 high = (hex >> 4) & 0xF;
343 return (conversion_u32[high] << 16) | conversion_u32[low];
344}
345
346// Unpack N tiles packed at 1bpp.
347static inline
348void
349unpack_tiles(u32 *src, u32 *dst, size_t n_tiles) {
350 u32 *target_src = src + n_tiles * 2;
351 while (src != target_src) {
352 *dst++ = unpack_1bb((*src >> 24) & 0xFF);
353 *dst++ = unpack_1bb((*src >> 16) & 0xFF);
354 *dst++ = unpack_1bb((*src >> 8) & 0xFF);
355 *dst++ = unpack_1bb(*src & 0xFF);
356 src++;
357 }
358}
359
360//
361// Direct Memory Access (DMA)
362//
363
364
365// Source, destination, and control registers.
366#define DMA_SRC(N) *((vu32*) 0x040000B0 + (N) * 12)
367#define DMA_DST(N) *((vu32*) 0x040000B4 + (N) * 12)
368#define DMA_CTRL(N) *((vu32*) 0x040000B8 + (N) * 12)
369
370// DMA control bits.
371#define DMA_DST_INC (0 << 0x15)
372#define DMA_DST_DEC (1 << 0x15)
373#define DMA_DST_FIXED (2 << 0x15)
374#define DMA_DST_RELOAD (3 << 0x15)
375#define DMA_SRC_INC (0 << 0x17)
376#define DMA_SRC_DEC (1 << 0x17)
377#define DMA_SRC_FIXED (2 << 0x17)
378#define DMA_REPEAT (1 << 0x19)
379#define DMA_CHUNK_16 (0 << 0x1A)
380#define DMA_CHUNK_32 (1 << 0x1A)
381#define DMA_NOW (0 << 0x1C)
382#define DMA_VBLANK (1 << 0x1C)
383#define DMA_HBLANK (2 << 0x1C)
384#define DMA_REFRESH (3 << 0x1C)
385#define DMA_IRQ (1 << 0x1E)
386#define DMA_ENABLE (1 << 0x1F)
387
388// Custom struct for cleaner DMA transfer functions.
389typedef struct DmaStr {
390 const void *src;
391 void *dst;
392 u32 ctrl;
393} DmaStr;
394
395#define DMA_TRANSFER ((volatile DmaStr*) 0x040000B0)
396
397// Transfer `count` number of chunks from src to dst using a DMA channel. Note
398// that chunks are not bytes, but instead configured based on bits set by
399// DMA_CTRL.
400inline
401void
402dma_transfer_copy(void *dst, const void *src, u32 count, int channel, u32 options) {
403 DMA_TRANSFER[channel].ctrl = 0;
404 DMA_TRANSFER[channel].src = src;
405 DMA_TRANSFER[channel].dst = dst;
406 DMA_TRANSFER[channel].ctrl = count | options;
407}
408
409inline
410void
411dma_transfer_fill(void *dst, volatile u32 src, u32 count, int channel, u32 options) {
412 DMA_TRANSFER[channel].ctrl = 0;
413 DMA_TRANSFER[channel].src = (const void *)&src;
414 DMA_TRANSFER[channel].dst = dst;
415 DMA_TRANSFER[channel].ctrl = count | options | DMA_SRC_FIXED;
416}
417
418// Copy N number of bytes using a DMA channel.
419inline
420void
421dma_copy(void *dst, const void *src, u32 size, int channel) {
422 dma_transfer_copy(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE);
423}
424
425// Fill the dst location with the word set at src.
426inline
427void
428dma_fill(void *dst, vu32 src, u32 size, int channel) {
429 dma_transfer_fill(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE);
430}
431
432//
433// Interrupts.
434//
435
436#define IRQ_ENABLE *((vu16*) 0x04000200)
437#define IRQ_ACK *((vu16*) 0x04000202)
438#define IRQ_CTRL *((vu16*) 0x04000208)
439#define IRQ_ACK_BIOS *((vu16*) 0x03007FF8)
440
441typedef enum {
442 IRQ_VBLANK,
443 IRQ_HBLANK,
444 IRQ_VCOUNT,
445 IRQ_TIMER_0,
446 IRQ_TIMER_1,
447 IRQ_TIMER_2,
448 IRQ_TIMER_3,
449 IRQ_SERIAL,
450 IRQ_DMA_0,
451 IRQ_DMA_1,
452 IRQ_DMA_2,
453 IRQ_DMA_3,
454 IRQ_KEYPAD,
455 IRQ_GAMEPAK,
456} IrqIndex;
457
458typedef void (*IrsFunc)(void);
459
460// Stub function pointer needed for when we want to enable interrupts that don't
461// require a custom function, such as for the BIOS VSync.
462void irs_stub(void);
463
464// Set and enable a given function in the interrupt table. If func is NULL, the
465// interrupt will be disabled instead.
466void irs_set(IrqIndex idx, IrsFunc func);
467
468// Initialize the function pointer for the main IRS routine written in ARM
469// assembly and enable interrupts.
470void irq_init(void);
471
472//
473// BIOS function declarations.
474//
475
476// These functions declarations can be used to call the BIOS functions from the
477// asm code.
478int bios_vblank_wait();
479int bios_div(int num, int denom);
480
481//
482// Sound.
483//
484
485// Sound registers.
486#define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60))
487#define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62))
488#define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64))
489#define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68))
490#define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C))
491#define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70))
492#define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72))
493#define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74))
494#define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78))
495#define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C))
496#define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80))
497#define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82))
498#define SOUND_STATUS *((vu16*)(MEM_IO + 0x84))
499#define SOUND_BIAS *((vu16*)(MEM_IO + 0x88))
500
501// Sound DMG master bits.
502#define SOUND_VOLUME_LEFT(N) (N)
503#define SOUND_VOLUME_RIGHT(N) ((N) << 4)
504#define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8)
505#define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9)
506#define SOUND_ENABLE_WAVE_LEFT (1 << 0xA)
507#define SOUND_ENABLE_NOISE_LEFT (1 << 0xB)
508#define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC)
509#define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD)
510#define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE)
511#define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF)
512
513typedef enum {
514 SOUND_DSOUND = (0x0 << 0),
515 SOUND_SQUARE1 = (0x1 << 0),
516 SOUND_SQUARE2 = (0x1 << 1),
517 SOUND_WAVE = (0x1 << 2),
518 SOUND_NOISE = (0x1 << 3),
519} SoundChannel;
520
521inline u16
522sound_volume(SoundChannel channels, u8 volume) {
523 volume = volume & 0x7;
524 channels = channels & 0xF;
525 return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC);
526}
527
528// Sound Direct Sound master bits.
529#define SOUND_DMG25 0x0
530#define SOUND_DMG50 0x1
531#define SOUND_DMG100 0x2
532#define SOUND_DSOUND_RATIO_A (1 << 0x2)
533#define SOUND_DSOUND_RATIO_B (1 << 0x3)
534#define SOUND_DSOUND_LEFT_A (1 << 0x8)
535#define SOUND_DSOUND_RIGHT_A (1 << 0x9)
536#define SOUND_DSOUND_TIMER_A (1 << 0xA)
537#define SOUND_DSOUND_RESET_A (1 << 0xB)
538#define SOUND_DSOUND_LEFT_B (1 << 0xC)
539#define SOUND_DSOUND_RIGHT_B (1 << 0xD)
540#define SOUND_DSOUND_TIMER_B (1 << 0xE)
541#define SOUND_DSOUND_RESET_B (1 << 0xF)
542
543// Direct sound FIFO queues.
544#define SOUND_FIFO_A ((u16*)(MEM_IO + 0xA0))
545#define SOUND_FIFO_B ((u16*)(MEM_IO + 0xA4))
546
547// Sound status bits.
548#define SOUND_ENABLE (1 << 0x7)
549
550// DMG square control bits.
551#define SOUND_SQUARE_LEN(N) (N)
552#define SOUND_SQUARE_DUTY(N) ((N) << 0x6)
553#define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8)
554#define SOUND_SQUARE_ENV_DIR(N) ((N) << 0xB)
555#define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC)
556
557// DMG square 1 sweep control bits.
558#define SOUND_SWEEP_NUMBER(N) (N)
559#define SOUND_SWEEP_DIR(N) ((N) << 0x3)
560#define SOUND_SWEEP_TIME(N) ((N) << 0x4)
561
562// DMG frequency bits (Square/Wave).
563#define SOUND_FREQ_TIMED (1 << 0xE)
564#define SOUND_FREQ_RESET (1 << 0xF)
565
566// DMG wave ram.
567#define SOUND_WAVE_RAM_0 *((vu32*)(MEM_IO + 0x90))
568#define SOUND_WAVE_RAM_1 *((vu32*)(MEM_IO + 0x94))
569#define SOUND_WAVE_RAM_2 *((vu32*)(MEM_IO + 0x98))
570#define SOUND_WAVE_RAM_3 *((vu32*)(MEM_IO + 0x9C))
571
572// DMG wave control bits.
573#define SOUND_WAVE_LENGTH(N) (N)
574#define SOUND_WAVE_MUTE 0x0
575#define SOUND_WAVE_VOL_100 (0x1 << 0xD)
576#define SOUND_WAVE_VOL_75 (0x4 << 0xD)
577#define SOUND_WAVE_VOL_50 (0x2 << 0xD)
578#define SOUND_WAVE_VOL_25 (0x3 << 0xD)
579
580// DMG wave mode bits.
581#define SOUND_WAVE_BANK_MODE(N) ((N) << 0x5)
582#define SOUND_WAVE_BANK_SELECT(N) ((N) << 0x6)
583#define SOUND_WAVE_ENABLE (1 << 0x7)
584
585typedef u8 WaveBank[32];
586
587#define SOUND_WAVE_RAM ((WaveBank*)(MEM_IO + 0x90))
588
589typedef enum {
590 NOTE_C_2 , NOTE_C_SHARP_2 , NOTE_D_2 , NOTE_D_SHARP_2 ,
591 NOTE_E_2 , NOTE_F_2 , NOTE_F_SHARP_2 , NOTE_G_2 ,
592 NOTE_G_SHARP_2 , NOTE_A_2 , NOTE_A_SHARP_2 , NOTE_B_2 ,
593 NOTE_C_3 , NOTE_C_SHARP_3 , NOTE_D_3 , NOTE_D_SHARP_3 ,
594 NOTE_E_3 , NOTE_F_3 , NOTE_F_SHARP_3 , NOTE_G_3 ,
595 NOTE_G_SHARP_3 , NOTE_A_3 , NOTE_A_SHARP_3 , NOTE_B_3 ,
596 NOTE_C_4 , NOTE_C_SHARP_4 , NOTE_D_4 , NOTE_D_SHARP_4 ,
597 NOTE_E_4 , NOTE_F_4 , NOTE_F_SHARP_4 , NOTE_G_4 ,
598 NOTE_G_SHARP_4 , NOTE_A_4 , NOTE_A_SHARP_4 , NOTE_B_4 ,
599 NOTE_C_5 , NOTE_C_SHARP_5 , NOTE_D_5 , NOTE_D_SHARP_5 ,
600 NOTE_E_5 , NOTE_F_5 , NOTE_F_SHARP_5 , NOTE_G_5 ,
601 NOTE_G_SHARP_5 , NOTE_A_5 , NOTE_A_SHARP_5 , NOTE_B_5 ,
602 NOTE_C_6 , NOTE_C_SHARP_6 , NOTE_D_6 , NOTE_D_SHARP_6 ,
603 NOTE_E_6 , NOTE_F_6 , NOTE_F_SHARP_6 , NOTE_G_6 ,
604 NOTE_G_SHARP_6 , NOTE_A_6 , NOTE_A_SHARP_6 , NOTE_B_6 ,
605 NOTE_C_7 , NOTE_C_SHARP_7 , NOTE_D_7 , NOTE_D_SHARP_7 ,
606 NOTE_E_7 , NOTE_F_7 , NOTE_F_SHARP_7 , NOTE_G_7 ,
607 NOTE_G_SHARP_7 , NOTE_A_7 , NOTE_A_SHARP_7 , NOTE_B_7 ,
608 NOTE_C_8
609} Note;
610
611static const u32 sound_rates[] = {
612 44 , 156 , 262 , 363 , 457 , 547 , 631 , 710 , 785 , 856 , 923 , 986 ,
613 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517,
614 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782,
615 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915,
616 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981,
617 1985, 1988, 1992, 1995, 1998, 2001, 2003, 2006, 2008, 2010, 2012, 2014,
618 2016,
619};
620
621//
622// System control.
623//
624
625// Used to configure gamepak access timings.
626#define SYSTEM_WAIT *((vu16*)(MEM_IO + 0x0204))
627
628// This register defaults to 0, but manufacture cartridges use the values
629// provided below.
630#define SYSTEM_WAIT_DEFAULT 0
631#define SYSTEM_WAIT_CARTRIDGE 0x4317
632
633//
634// Misc.
635//
636
637// Custom VSync option. This will waste a lot of battery power, since the
638// machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts
639// are enabled.
640static inline void
641wait_vsync(void) {
642 while(DISP_VCOUNT >= 160);
643 while(DISP_VCOUNT < 160);
644}
645
646// General utility macros.
647#define MIN(A, B) ((A) <= (B) ? (A) : (B))
648#define MAX(A, B) ((A) >= (B) ? (A) : (B))
649#define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X))
650#define LEN(ARR) (sizeof(ARR) / sizeof((ARR)[0]))
651
652// Fixed-point arithmetic for (i.P) numbers.
653#define FP_MUL(A,B,P) (((A) * (B)) >> (P))
654#define FP_DIV(A,B,P) (((A) << (P)) / (B))
655#define FP_LERP(Y0,Y1,X,P) ((Y0) + FP_MUL((X), ((Y1) - (Y0)), P))
656
657//
658// Memory section macros for devkitARM.
659//
660
661#define IWRAM_DATA __attribute__((section(".iwram")))
662#define IWRAM_CODE __attribute__((section(".iwram"), long_call, target("arm")))
663#define EWRAM_DATA __attribute__((section(".ewram")))
664#define EWRAM_CODE __attribute__((section(".ewram"), long_call))
665#define EWRAM_BSS __attribute__((section(".sbss")))
666
667#endif // COMMON_H
diff --git a/src/filesystem.c b/src/filesystem.c
new file mode 100644
index 0000000..00c0605
--- /dev/null
+++ b/src/filesystem.c
@@ -0,0 +1,409 @@
1/*
2Copyright (c) 2021 Bad Diode
3
4Permission to use, copy, modify, and distribute this software for any
5purpose with or without fee is hereby granted, provided that the above
6copyright notice and this permission notice appear in all copies.
7
8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9WITH REGARD TO THIS SOFTWARE.
10*/
11
12#include <string.h>
13
14#include "filesystem.h"
15
16// This file implements a filesystem with a minimum block size of 256 bytes. The
17// maximum number of files depends on the block size. The default 1KB block size
18// will give us 32-64 files depending on the size of MEM_CART. In case we want
19// to use a block size of 512 bytes, we will have up to 128 file available.
20// Blocks of 256 bytes will give us the maximum of 255 files available, since
21// a block index of 0xFF will be considered as a null block.
22
23// A fileblock of 1KB give us a maximum of 64 files.
24#define FILE_BLOCK_SIZE KB(1)
25#define FILE_MAX_FILES 64
26#define FILE_N_BLOCKS 62
27
28// With this file name size sizeof(FileIndex) will be 32 bytes. 32 * 64 files
29// give us 2KB spent on file index that we can't use for data (so maximum of 62
30// files without accounting for the block index).
31#define FILE_NAME_SIZE 30
32#define FILE_INDEX_NUM 62
33
34// Since we are reserving the first 2K bytes for the filesystem, we have 60
35// blocks available for writing data. If you were to change the previous
36// parameters, you *must* recalculate the initial block start location.
37#define FILE_DATA_START KB(2)
38
39// We must write to the SRAM using the 8bit bus.
40#define SRAM ((vu8*)(MEM_CART))
41
42// Special filesystem constants.
43enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF };
44
45typedef struct FileBlock {
46 // Size used in the current block (in bytes). Should be smaller than:
47 // FILE_BLOCK_SIZE - sizeof(FileBlock)
48 u16 size;
49 // The index for the next block. Set to FS_NULL if there is none.
50 u8 next_block;
51 u8 prev_block;
52} FileBlock;
53
54typedef struct FileIndex {
55 // File name.
56 char name[FILE_NAME_SIZE + 1];
57 // Index to the first block of this file. If set to FS_NULL this file
58 // has not yet been written to.
59 u8 first_block;
60} FileIndex;
61
62// The filesystem header.
63typedef struct FileSystem {
64 // The first byte of the SRAM can become corrupted in some situations, like
65 // changing cartridges for example.
66 u8 blank;
67 // If the filesystem exists, this will be set to FS_INIT_PATTERN.
68 u8 initialized;
69 // Number of blocks in use.
70 u8 busy_blocks;
71 // Number of files currently existing in the filesystem.
72 u8 num_files;
73 // This stores a bitmap pattern to keep track of the blocks in use by the
74 // filesystem. The first byte maps the first 8 blocks and so on.
75 u8 used_blocks[FILE_MAX_FILES / 8];
76 // The list of possible file indexes.
77 FileIndex files[FILE_INDEX_NUM];
78} FileSystem;
79
80#define FILE_BLOCK_CAPACITY (FILE_BLOCK_SIZE - sizeof(FileBlock))
81
82EWRAM_BSS
83static FileSystem filesystem;
84
85void
86_fs_read(u8 *dst, u16 pos, u16 n_bytes) {
87 for (size_t i = 0; i < n_bytes; ++i) {
88 dst[i] = SRAM[pos + i];
89 }
90}
91
92void
93_fs_write(u8 *src, u16 pos, u16 n_bytes) {
94 for (size_t i = 0; i < n_bytes; ++i) {
95 SRAM[pos + i] = src[i];
96 }
97}
98
99void
100fs_init(void) {
101 // Load filesystem if existing.
102 _fs_read(&filesystem, 0, sizeof(FileSystem));
103 if (filesystem.initialized != FS_INIT_PATTERN) {
104 // Clear SRAM.
105 for (size_t i = 0; i < KB(64) / 8; ++i) {
106 SRAM[i] = 0x00;
107 }
108
109 // Initialize block headers.
110 FileBlock block = {
111 .size = 0,
112 .next_block = FS_NULL,
113 .prev_block = FS_NULL,
114 };
115 for (size_t i = 0; i < FILE_INDEX_NUM; ++i) {
116 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * i;
117 _fs_write(&block, block_pos, sizeof(FileBlock));
118 }
119
120 // Initialize filesystem.
121 dma_fill(&filesystem, 0, sizeof(FileSystem), 3);
122 filesystem.initialized = FS_INIT_PATTERN;
123 for (size_t i = 0; i < FILE_INDEX_NUM; ++i) {
124 filesystem.files[i].first_block = FS_NULL;
125 }
126
127 // Write the FS to disk.
128 _fs_write(&filesystem, 0, sizeof(FileSystem));
129 }
130}
131
132void
133_fs_update_filesystem_header(void) {
134 _fs_write(&filesystem, 0, offsetof(FileSystem, files));
135}
136
137void
138_fs_update_file_index(u16 index) {
139 _fs_write(&filesystem.files[index],
140 offsetof(FileSystem, files) + index * sizeof(FileIndex),
141 sizeof(FileIndex));
142}
143
144File
145fs_open_file(char *name, OpenMode mode) {
146 // Try to find an existing file.
147 for (size_t i = 0; i < filesystem.num_files; ++i) {
148 // TODO: Replace strcmp with vectorized fixed size char comparison.
149 if (strcmp(name, filesystem.files[i].name) == 0) {
150 return (File){i, 0, mode};
151 }
152 }
153
154 // If read only.
155 if ((mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) {
156 return (File){FS_NULL, 0, mode};
157 }
158
159 // Create a new file if there is space.
160 if (filesystem.num_files < FILE_INDEX_NUM) {
161 u16 index = filesystem.num_files++;
162 u16 k = 0;
163 while(*name) {
164 filesystem.files[index].name[k++] = *name++;
165 }
166
167 // Update file index and filesystem on SRAM.
168 _fs_update_file_index(index);
169 _fs_update_filesystem_header();
170
171 return (File){index, 0, mode};
172 }
173 return (File){FS_NULL, 0, mode};
174}
175
176u16
177fs_file_size(File *file) {
178 u16 size = 0;
179 FileBlock block;
180 u16 blk_id = filesystem.files[file->index].first_block;
181 while (blk_id != FS_NULL) {
182 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
183 _fs_read(&block, block_pos, sizeof(FileBlock));
184 size += block.size;
185 blk_id = block.next_block;
186 }
187 return size;
188}
189
190u8
191_fs_init_new_block(void) {
192 // Find free block.
193 u8 block_index = 0;
194 for (size_t j = 0; j < LEN(filesystem.used_blocks); ++j) {
195 for (size_t i = 0; i < 8; ++i, block_index++) {
196 u8 blk = (filesystem.used_blocks[j] >> i) & 0x1;
197 if (blk == 0) {
198 // Initialize the block.
199 filesystem.busy_blocks++;
200 filesystem.used_blocks[j] |= (1 << i);
201 _fs_update_filesystem_header();
202 return block_index;
203 }
204 }
205 }
206 return FS_NULL;
207}
208
209// Recursively free blocks starting at blk_id. To improve performance, the
210// filesystem header is updated in memory but not written to disk. It is
211// responsability of the caller to perform the filesystem update.
212void
213_fs_free_blocks(u8 blk_id) {
214 if (blk_id == FS_NULL) {
215 return;
216 }
217
218 // Read block.
219 FileBlock block;
220 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
221 _fs_read(&block, block_pos, sizeof(FileBlock));
222
223 // Update block.
224 u8 next_block = block.next_block;
225 block = (FileBlock){
226 .size = 0,
227 .next_block = FS_NULL,
228 .prev_block = FS_NULL,
229 };
230 _fs_write(&block, block_pos, sizeof(FileBlock));
231
232 // Update dirty and busy blocks.
233 filesystem.busy_blocks--;
234 filesystem.used_blocks[blk_id / 8] &= ~(1 << (blk_id % 8));
235
236 _fs_free_blocks(next_block);
237}
238
239void
240_fs_write_to_block(u8 *src, u16 n_bytes, u16 blk_offset,
241 u8 blk_id, u8 prev_blk, bool append) {
242 // Read initial block.
243 FileBlock block;
244 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
245 _fs_read(&block, block_pos, sizeof(FileBlock));
246 u16 block_capacity = FILE_BLOCK_CAPACITY - blk_offset;
247
248 // Write capacity.
249 u16 block_bytes = MIN(block_capacity, n_bytes);
250 _fs_write(src, block_pos + sizeof(FileBlock) + blk_offset, block_bytes);
251
252 if (n_bytes > block_capacity) {
253 if (block.next_block == FS_NULL) {
254 // Find new available block and initialize it.
255 block.next_block = _fs_init_new_block();
256 }
257 _fs_write_to_block(src + block_capacity, n_bytes - block_capacity, 0,
258 block.next_block, blk_id, append);
259 } else if (block.next_block != FS_NULL){
260 // Recursively free unused blocks.
261 _fs_free_blocks(block.next_block);
262 _fs_update_filesystem_header();
263 block.next_block = FS_NULL;
264 }
265
266 // Update block header.
267 if (prev_blk != FS_NULL) {
268 block.prev_block = prev_blk;
269 }
270 block.size = block_bytes;
271 _fs_write(&block, block_pos, sizeof(FileBlock));
272}
273
274int
275fs_seek(File *file, int offset, SeekMode mode) {
276 u16 file_size = fs_file_size(file);
277 u16 new_offset = 0;
278 switch (mode) {
279 case FS_SEEK_SET: {
280 new_offset = offset;
281 } break;
282 case FS_SEEK_CUR: {
283 new_offset = MAX((int)file->offset + offset, 0);
284 } break;
285 case FS_SEEK_END: {
286 new_offset = MAX((int)file_size - 1 + offset, 0);
287 } break;
288 }
289 if (new_offset != 0 && new_offset >= file_size) {
290 return -1;
291 }
292 file->offset = new_offset;
293 return 0;
294}
295
296u16
297fs_write(u8 *src, u16 n_bytes, File *file) {
298 if ((file->mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) {
299 return 0;
300 }
301
302 FileIndex *file_idx = &filesystem.files[file->index];
303
304 u8 blk_id = FS_NULL;
305 u8 blk_prev = FS_NULL;
306 u16 offset = file->offset;
307 if (file_idx->first_block == FS_NULL) {
308 // Check how many blocks will this write requires and if we have enough
309 // available.
310 u16 blocks_required = n_bytes / FILE_BLOCK_CAPACITY;
311 u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks;
312 if (blocks_required > blocks_available) {
313 return 0;
314 }
315
316 // Find the first available block.
317 blk_id = _fs_init_new_block();
318 file_idx->first_block = blk_id;
319
320 // Update file index on SRAM.
321 _fs_update_file_index(file->index);
322 } else {
323 // Check how many blocks will this write requires and if we have
324 // enough available.
325 u16 file_size = fs_file_size(file);
326 u16 blocks_in_file = file_size / FILE_BLOCK_SIZE;
327 u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks + blocks_in_file;
328 u16 blocks_required = (n_bytes + offset) / FILE_BLOCK_CAPACITY;
329 if (blocks_required > blocks_available) {
330 return 0;
331 }
332
333 blk_id = file_idx->first_block;
334
335 // If there is an offset find the block index and relative offset.
336 if (offset >= FILE_BLOCK_CAPACITY) {
337 u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY;
338 for (size_t i = 0; i < n_blocks_offset; ++i) {
339 FileBlock block;
340 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
341 _fs_read(&block, block_pos, sizeof(FileBlock));
342 blk_id = block.next_block;
343 blk_prev = block.prev_block;
344 if (blk_id == FS_NULL) {
345 return 0;
346 }
347 }
348 offset = offset % FILE_BLOCK_CAPACITY;
349 }
350 }
351
352 // Write to block.
353 _fs_write_to_block(src, n_bytes, offset, blk_id, blk_prev,
354 file->mode == FS_OPEN_APPEND);
355
356 return n_bytes;
357}
358
359void
360_fs_read_from_block(u8 *dst, u16 n_bytes, u16 blk_offset, u8 blk_id) {
361 // Read initial block.
362 FileBlock block;
363 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
364 _fs_read(&block, block_pos, sizeof(FileBlock));
365
366 u16 read_bytes = MIN(block.size - blk_offset, n_bytes);
367 _fs_read(dst, block_pos + blk_offset + sizeof(FileBlock), read_bytes);
368
369 u16 remaining_bytes = n_bytes - read_bytes;
370 if (block.next_block != FS_NULL && remaining_bytes > 0) {
371 _fs_read_from_block(dst + read_bytes, remaining_bytes, 0, block.next_block);
372 }
373}
374
375u16
376fs_read(u8 *dst, u16 n_bytes, File *file) {
377 if ((file->mode & FS_OPEN_READ) == 0) {
378 return 0;
379 }
380
381 // If there is an offset find the block index and relative offset.
382 u8 blk_id = filesystem.files[file->index].first_block;
383 u16 offset = file->offset;
384
385 // Read as much as we can from the file after the offset.
386 u16 file_size = fs_file_size(file);
387 if (offset + n_bytes >= file_size) {
388 n_bytes = file_size - offset;
389 }
390
391 if (offset >= FILE_BLOCK_CAPACITY) {
392 u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY;
393 for (size_t i = 0; i < n_blocks_offset; ++i) {
394 FileBlock block;
395 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
396 _fs_read(&block, block_pos, sizeof(FileBlock));
397 blk_id = block.next_block;
398 if (blk_id == FS_NULL) {
399 return 0;
400 }
401 }
402 offset = offset % FILE_BLOCK_CAPACITY;
403 }
404
405 // Copy n_bytes to destination.
406 _fs_read_from_block(dst, n_bytes, offset, blk_id);
407
408 return n_bytes;
409}
diff --git a/src/filesystem.h b/src/filesystem.h
new file mode 100644
index 0000000..d50e5e7
--- /dev/null
+++ b/src/filesystem.h
@@ -0,0 +1,36 @@
1#ifndef FILESYSTEM_H
2#define FILESYSTEM_H
3
4#include "common.h"
5
6typedef enum {
7 FS_OPEN_READ = (1 << 0),
8 FS_OPEN_WRITE = (1 << 1),
9 FS_OPEN_APPEND = (1 << 2),
10} OpenMode;
11
12typedef struct File {
13 // File index offset.
14 u8 index;
15 // The offset within the file. Must always be valid, and so the File struct
16 // shouldn't be manaully modified unless we are sure we know what we are
17 // doing.
18 u16 offset;
19 // The mode of this file (read/write/append).
20 OpenMode mode;
21} File;
22
23typedef enum {
24 FS_SEEK_SET,
25 FS_SEEK_CUR,
26 FS_SEEK_END,
27} SeekMode;
28
29void fs_init(void);
30File fs_open_file(char *name, OpenMode mode);
31u16 fs_file_size(File *file);
32int fs_seek(File *file, int offset, SeekMode mode);
33u16 fs_write(u8 *src, u16 n_bytes, File *file);
34u16 fs_read(u8 *dst, u16 n_bytes, File *file);
35
36#endif // FILESYSTEM_H
diff --git a/src/interrupts.c b/src/interrupts.c
new file mode 100644
index 0000000..8e560dc
--- /dev/null
+++ b/src/interrupts.c
@@ -0,0 +1,91 @@
1#include "common.h"
2
3IrsFunc irs_table[] = {
4 [IRQ_VBLANK ] = NULL,
5 [IRQ_HBLANK ] = NULL,
6 [IRQ_VCOUNT ] = NULL,
7 [IRQ_TIMER_0] = NULL,
8 [IRQ_TIMER_1] = NULL,
9 [IRQ_TIMER_2] = NULL,
10 [IRQ_TIMER_3] = NULL,
11 [IRQ_SERIAL ] = NULL,
12 [IRQ_DMA_0 ] = NULL,
13 [IRQ_DMA_1 ] = NULL,
14 [IRQ_DMA_2 ] = NULL,
15 [IRQ_DMA_3 ] = NULL,
16 [IRQ_KEYPAD ] = NULL,
17 [IRQ_GAMEPAK] = NULL,
18};
19
20// External irs_main function, has to be written in ARM assembly.
21void irs_main(void);
22#define IRS_MAIN *(IrsFunc*)(0x03007FFC)
23
24void
25irq_enable(IrqIndex idx) {
26 switch (idx) {
27 case IRQ_VBLANK: { DISP_STATUS |= DISP_VBLANK_IRQ; } break;
28 case IRQ_HBLANK: { DISP_STATUS |= DISP_HBLANK_IRQ; } break;
29 case IRQ_VCOUNT: { DISP_STATUS |= DISP_VCOUNT_IRQ; } break;
30 case IRQ_TIMER_0: { TIMER_CTRL_0 |= TIMER_CTRL_IRQ; } break;
31 case IRQ_TIMER_1: { TIMER_CTRL_1 |= TIMER_CTRL_IRQ; } break;
32 case IRQ_TIMER_2: { TIMER_CTRL_2 |= TIMER_CTRL_IRQ; } break;
33 case IRQ_TIMER_3: { TIMER_CTRL_3 |= TIMER_CTRL_IRQ; } break;
34 case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break;
35 case IRQ_DMA_0: { DMA_CTRL(0) |= DMA_IRQ; } break;
36 case IRQ_DMA_1: { DMA_CTRL(1) |= DMA_IRQ; } break;
37 case IRQ_DMA_2: { DMA_CTRL(2) |= DMA_IRQ; } break;
38 case IRQ_DMA_3: { DMA_CTRL(3) |= DMA_IRQ; } break;
39 case IRQ_KEYPAD: { KEY_CTRL |= KEY_IRQ; } break;
40 case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break;
41 }
42 IRQ_ENABLE |= (1 << idx);
43}
44
45void
46irq_disable(IrqIndex idx) {
47 switch (idx) {
48 case IRQ_VBLANK: { DISP_STATUS &= ~DISP_VBLANK_IRQ; } break;
49 case IRQ_HBLANK: { DISP_STATUS &= ~DISP_HBLANK_IRQ; } break;
50 case IRQ_VCOUNT: { DISP_STATUS &= ~DISP_VCOUNT_IRQ; } break;
51 case IRQ_TIMER_0: { TIMER_CTRL_0 &= ~TIMER_CTRL_IRQ; } break;
52 case IRQ_TIMER_1: { TIMER_CTRL_1 &= ~TIMER_CTRL_IRQ; } break;
53 case IRQ_TIMER_2: { TIMER_CTRL_2 &= ~TIMER_CTRL_IRQ; } break;
54 case IRQ_TIMER_3: { TIMER_CTRL_3 &= ~TIMER_CTRL_IRQ; } break;
55 case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break;
56 case IRQ_DMA_0: { DMA_CTRL(0) &= ~DMA_IRQ; } break;
57 case IRQ_DMA_1: { DMA_CTRL(1) &= ~DMA_IRQ; } break;
58 case IRQ_DMA_2: { DMA_CTRL(2) &= ~DMA_IRQ; } break;
59 case IRQ_DMA_3: { DMA_CTRL(3) &= ~DMA_IRQ; } break;
60 case IRQ_KEYPAD: { KEY_CTRL &= ~KEY_IRQ; } break;
61 case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break;
62 }
63 IRQ_ENABLE &= ~(1 << idx);
64}
65
66void
67irs_set(IrqIndex idx, IrsFunc func) {
68 // Store IRQ_CTRL status and disable interrupts for now.
69 u16 irq_ctrl = IRQ_CTRL;
70 IRQ_CTRL = 0;
71
72 // Update the IRS table and enable/disable the given IRQ.
73 irs_table[idx] = func;
74 if (func == NULL) {
75 irq_disable(idx);
76 } else {
77 irq_enable(idx);
78 }
79
80 // Restore previous irq_ctrl.
81 IRQ_CTRL = irq_ctrl;
82}
83
84void
85irq_init(void) {
86 IRS_MAIN = irs_main;
87 IRQ_CTRL = 1;
88}
89
90void
91irs_stub(void) {}
diff --git a/src/interrupts.s b/src/interrupts.s
new file mode 100644
index 0000000..67b9fe9
--- /dev/null
+++ b/src/interrupts.s
@@ -0,0 +1,89 @@
1 .file "interrupts.s"
2 .extern irs_table;
3 .section .iwram, "ax", %progbits
4 .arm
5 .align
6 .global irs_main
7
8irs_main:
9 @ Get the contents of IRQ_ENABLE, IRQ_ACK, and IRQ_CTRL
10 ldr ip, mem_irq_base_reg @ ip = (IRQ_ENABLE << 16) | IRQ_ACK
11 ldr r0, [ip] @ r0 = irq_enable
12 and r1, r0, r0, lsr #16 @ r1 = irq_enable & irq_ack
13
14 @ Disable IRQ_CTRL for now.
15 mov r3, #0 @ r3 = 0
16 strh r3, [ip, #8] @ *(ip + 0x8) = r3
17
18 @ r0 = irs_table address pointer
19 @ r2 = tmp
20 @ r3 = 0 (for r3; r3 < 14; r3++)
21 ldr r0, = irs_table
22irs_main_fp_search:
23 @ Check that the current index is an active IRQ.
24 mov r2, #1
25 and r2, r1, r2, lsl r3
26 cmp r2, #0
27 beq irs_main_fp_search_not_enabled
28
29 @ Extract the function pointer for this IRS if available.
30 ldr r2, [r0]
31 cmp r2, #0
32 bne irs_main_handle_irs
33
34irs_main_fp_search_not_enabled:
35 add r0, r0, #4
36 add r3, #1
37 cmp r3, #14
38 bne irs_main_fp_search
39 b irs_main_exit
40
41irs_main_handle_irs:
42 @ r2: Contains IRQ function pointer.
43 @ r3: Stores the IRQ index.
44
45 @ Acknowledge that we are handling this interrupt writing to IRQ_ACK and
46 @ IRQ_ACK_BIOS.
47 mov r0, #1
48 lsl r0, r0, r3
49 strh r0, [ip, #2]
50 ldr r1, mem_irq_ack_bios @ r1 = IRQ_ACK_BIOS
51 str r0, [r1]
52
53 @ Store the SPSR in one of the free registers and save it along with the
54 @ return pointer.
55 mrs r3, spsr
56 stmfd sp!, {r3, lr}
57
58 @ Set CPU to system mode
59 mrs r3, cpsr
60 bic r3, r3, #0xDF
61 orr r3, r3, #0x1F
62 msr cpsr, r3
63
64 @ Call isr function pointer
65 stmfd sp!, {lr}
66 mov lr, pc
67 bx r2
68 ldmfd sp!, {lr}
69
70 @ Set CPU to irq mode
71 mrs r3, cpsr
72 bic r3, r3, #0xDF
73 orr r3, r3, #0x92
74 msr cpsr, r3
75
76 @ Restore the SPSR and the registers.
77 ldmfd sp!, {r3, lr}
78 msr spsr, r3
79
80irs_main_exit:
81 @ Restore IRQ_CTRL.
82 mov r3, #1 @ r3 = 0
83 strh r3, [ip, #8] @ *(ip + 0x8) = r3
84 bx lr
85
86mem_irq_base_reg:
87 .word 0x04000200 @ IRQ_ENABLE
88mem_irq_ack_bios:
89 .word 0x03007FF8 @ IRQ_ACK_BIOS
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..e228034
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,81 @@
1/*
2Copyright (c) 2021 Bad Diode
3
4Permission to use, copy, modify, and distribute this software for any
5purpose with or without fee is hereby granted, provided that the above
6copyright notice and this permission notice appear in all copies.
7
8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9WITH REGARD TO THIS SOFTWARE.
10*/
11
12#include "common.h"
13// #include "text.h"
14
15#include "interrupts.c"
16#include "filesystem.c"
17#include "renderer.c"
18
19//
20// Config parameters.
21//
22
23#ifdef PROF_ENABLE
24#if PROF_ENABLE == 0
25#define PROF(F,VAR) (profile_start(),(F),(VAR) = profile_stop())
26#elif PROF_ENABLE == 1
27#define PROF(F,VAR) (profile_start(),(F),(VAR) = MAX(profile_stop(), (VAR)))
28#endif
29#ifndef PROF_SHOW_X
30#define PROF_SHOW_X 0
31#endif
32#ifndef PROF_SHOW_Y
33#define PROF_SHOW_Y 0
34#endif
35#define PROF_SHOW() \
36 do { \
37 txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\
38 txt_printf("EVAL: %lu ", eval_cycles);\
39 txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\
40 txt_printf("FLIP: %lu ", flip_cycles);\
41 } while (0)
42#define PROF_INIT() \
43 u32 eval_cycles = 0;\
44 u32 flip_cycles = 0;
45#else
46#define PROF(F,VAR) (F)
47#define PROF_SHOW()
48#define PROF_INIT()
49#endif
50
51int main(void) {
52 // Adjust system wait times.
53 SYSTEM_WAIT = SYSTEM_WAIT_CARTRIDGE;
54
55 // Initialize filesystem.
56 fs_init();
57
58 // Initialize renderer.
59 renderer_init();
60
61 // Register interrupts.
62 irq_init();
63 irs_set(IRQ_VBLANK, irs_stub);
64
65 draw_pixel(0, 0, 1);
66 draw_pixel(0, 1, 2);
67 draw_pixel(0, 2, 3);
68 draw_pixel(0, 3, 4);
69
70 // Main loop.
71 PROF_INIT();
72 size_t frame_counter = 0;
73 while(true) {
74 bios_vblank_wait();
75 PROF_SHOW();
76 PROF(flip_buffer(), flip_cycles);
77 frame_counter++;
78 }
79
80 return 0;
81}
diff --git a/src/renderer.c b/src/renderer.c
new file mode 100644
index 0000000..9fe55b2
--- /dev/null
+++ b/src/renderer.c
@@ -0,0 +1,86 @@
1// TODO: For now we pack front/backbuffers together but this make it so that we
2// can only use 2 backgrounds. Instead we can move the backbuffer to the end of
3// the VRAM. This will give us 3 backgrounds but eats into the available memory
4// for sprites but should be fine for non sprite intensive applications.
5#define FRONTBUFFER ((u32*)(MEM_VRAM))
6#define BACKBUFFER ((u32*)(MEM_VRAM + KB(96) - KB(20)))
7
8// Adjust both of these if the location of the map changes. Each screnblock
9// requires 2K.
10#define FRONTBUFFER_TILEMAP ((u16*)(MEM_VRAM + KB(20)))
11#define FRONTBUFFER_SCREENBLOCK 10
12
13static u32 dirty_tiles[21] = {0};
14
15// TODO: Allow disable bound checking at compile time.
16#define BOUNDCHECK_SCREEN() if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return;
17
18IWRAM_CODE
19void
20draw_pixel(u16 x, u16 y, u8 color) {
21 BOUNDCHECK_SCREEN();
22
23 // Find row position for the given x/y coordinates.
24 size_t tile_x = x / 8;
25 size_t tile_y = y / 8;
26 size_t start_col = x % 8;
27 size_t start_row = y % 8;
28 size_t pos = start_row + (tile_x + tile_y * 32) * 8;
29
30 // Update backbuffer.
31 size_t shift = start_col * sizeof(u32);
32 BACKBUFFER[pos] = (BACKBUFFER[pos] & ~(0xF << shift)) | color << shift;
33
34 // Mark tile as dirty.
35 dirty_tiles[tile_y] |= 1 << tile_x;
36}
37
38IWRAM_CODE
39void
40flip_buffer(void) {
41 // Copy dirty tiles from the backbuffer to the frontbuffer.
42 Tile *dst = FRONTBUFFER;
43 Tile *src = BACKBUFFER;
44 for (size_t j = 0; j < 20; ++j) {
45 if (dirty_tiles[j] == 0) {
46 continue;
47 }
48 for (size_t i = 0, k = 1; i < 30; ++i, k <<= 1) {
49 if (dirty_tiles[j] & k) {
50 dst[i + j * 32] = src[i + j * 32];
51 }
52 }
53 dirty_tiles[j] = 0;
54 }
55}
56
57void
58renderer_init(void) {
59 // Initialize display mode and bg palette.
60 DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_OBJ;
61
62 // Initialize backgrounds.
63 BG_CTRL(0) = BG_CHARBLOCK(0) | BG_SCREENBLOCK(FRONTBUFFER_SCREENBLOCK);
64
65 // TODO: Initialize other backgrounds if needed.
66
67 // Use DMA to clear front and back buffers.
68 dma_fill(FRONTBUFFER, 0, KB(20), 3);
69 dma_fill(BACKBUFFER, 0, KB(20), 3);
70
71 // Initialize default palette.
72 PAL_BUFFER_BG[0] = COLOR_BLACK;
73 PAL_BUFFER_BG[1] = COLOR_WHITE;
74 PAL_BUFFER_BG[2] = COLOR_RED;
75 PAL_BUFFER_BG[3] = COLOR_BLUE;
76 PAL_BUFFER_BG[4] = COLOR_CYAN;
77 PAL_BUFFER_BG[5] = COLOR_GREY;
78
79 // Initialize background memory map.
80 for (size_t i = 0; i < 32 * 20; ++i) {
81 FRONTBUFFER_TILEMAP[i] = i;
82 }
83
84// // Load font data into VRAM.
85// unpack_tiles(&bd_font, FONT_DATA, 256);
86}
diff --git a/src/shorthand.h b/src/shorthand.h
new file mode 100644
index 0000000..08cc7fe
--- /dev/null
+++ b/src/shorthand.h
@@ -0,0 +1,47 @@
1/*
2Copyright (c) 2021 Bad Diode
3
4Permission to use, copy, modify, and distribute this software for any
5purpose with or without fee is hereby granted, provided that the above
6copyright notice and this permission notice appear in all copies.
7
8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9WITH REGARD TO THIS SOFTWARE.
10*/
11
12#ifndef UTILS_SHORTHAND_H
13#define UTILS_SHORTHAND_H
14
15#include <assert.h>
16#include <stdbool.h>
17#include <stddef.h>
18#include <stdint.h>
19
20//
21// This simple header just typedefs the basic C define types to a shorter name,
22// loads the quality of life bool macro for _Bool and defines shorthand macros
23// for byte sizes.
24
25typedef uint8_t u8;
26typedef uint16_t u16;
27typedef uint32_t u32;
28typedef uint64_t u64;
29typedef int8_t s8;
30typedef int16_t s16;
31typedef int32_t s32;
32typedef int64_t s64;
33typedef volatile u8 vu8;
34typedef volatile u16 vu16;
35typedef volatile u32 vu32;
36typedef volatile u64 vu64;
37typedef volatile s8 vs8;
38typedef volatile s16 vs16;
39typedef volatile s32 vs32;
40typedef volatile s64 vs64;
41
42#define KB(N) ((u64)(N) * 1024)
43#define MB(N) ((u64)KB(N) * 1024)
44#define GB(N) ((u64)MB(N) * 1024)
45#define TB(N) ((u64)GB(N) * 1024)
46
47#endif // UTILS_SHORTHAND_H