tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 6fc86a2c2d69add4cca144d6d4fce21a47880bbb
parent 7475a648273bebf997b8fecaa9fa6464037d6c82
Author: Connor Thomson <connor@gnufault.org>
Date: Sun, 25 Oct 2026 15:10:31 -0700
Merge branch 'ps1'
Diffstat:
5 files changed, 591 insertions(+), 0 deletions(-)
diff --git a/.gitmodules b/.gitmodules
@@ -7,3 +7,6 @@
[submodule "platforms/saturn/joengine"]
path = platforms/saturn/joengine
url = https://github.com/johannes-fetz/joengine.git
+[submodule "platforms/ps1/nugget"]
+ path = platforms/ps1/nugget
+ url = https://github.com/pcsx-redux/nugget.git
diff --git a/README.md b/README.md
@@ -9,6 +9,7 @@ Tuxánci currently supports:
- DOS
- Dreamcast
- N64
+- PS1
- Saturn
- SDL2
- SDL3
diff --git a/platforms/ps1/.gitignore b/platforms/ps1/.gitignore
@@ -0,0 +1,2 @@
+tuxanci2.cue
+tuxanci2.bin
diff --git a/platforms/ps1/Makefile b/platforms/ps1/Makefile
@@ -0,0 +1,141 @@
+MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+ROOT_DIR := $(abspath $(MAKEFILE_DIR)/../..)
+
+NUGGET ?= $(MAKEFILE_DIR)nugget
+NUGGET := $(abspath $(NUGGET))
+
+PS1_PREFIX ?= $(firstword $(foreach p,mipsel-none-elf mipsel-linux-gnu,$(if $(shell command -v $(p)-gcc 2>/dev/null),$(p))))
+ifeq ($(PS1_PREFIX),)
+$(error You need to install mipsel-none-elf-gcc or mipsel-linux-gnu-gcc or set PS1_PREFIX)
+endif
+PS1_OFORMAT ?= $(if $(filter mipsel-none-elf,$(PS1_PREFIX)),elf32-littlemips,elf32-tradlittlemips)
+PS1_CC := $(PS1_PREFIX)-gcc
+PS1_AR := $(PS1_PREFIX)-gcc-ar
+PS1_OBJCOPY := $(PS1_PREFIX)-objcopy
+MKPSXISO ?= mkpsxiso
+
+PSXLICENSEPATCH ?= psxlicensepatch
+PSXLICENSEDUMP ?= psxlicensedump
+
+PS1_REGION ?= US
+PS1_TEXT ?=
+PS1_TMD ?=
+
+PS1_BUILD ?= build/ps1
+PS1_BUILD_DIR := $(ROOT_DIR)/$(PS1_BUILD)
+PS1_LIBRARY := $(PS1_BUILD_DIR)/tuxanci2.a
+OBJ_DIR := $(PS1_BUILD_DIR)/platform
+ISO_DIR := $(PS1_BUILD_DIR)/iso
+
+ifeq ($(wildcard $(NUGGET)/ps-exe.ld),)
+$(error nugget not found at '$(NUGGET)'.)
+endif
+
+PS1_ELF := $(PS1_BUILD_DIR)/psx.elf
+PS1_MAP := $(PS1_BUILD_DIR)/psx.map
+PS1_EXE := $(ISO_DIR)/PSX.EXE
+SYSTEM_CNF := $(ISO_DIR)/SYSTEM.CNF
+ISO_XML := $(ISO_DIR)/tuxanci2.xml
+
+TARGET_BIN := $(MAKEFILE_DIR)tuxanci2.bin
+TARGET_CUE := $(MAKEFILE_DIR)tuxanci2.cue
+
+ARCHFLAGS := -march=mips1 -mabi=32 -EL -fno-pic -mno-shared -mno-abicalls -mfp32 -msoft-float -mno-llsc -G0 \
+ -fno-stack-protector -nostdlib -ffreestanding
+COMMONFLAGS := -ffunction-sections -fdata-sections -mno-gpopt -fomit-frame-pointer -fno-builtin \
+ -fno-strict-aliasing -Wno-attributes
+
+PLATFORM_CFLAGS := -O2 $(ARCHFLAGS) $(COMMONFLAGS) -Wall -Wextra \
+ -I$(ROOT_DIR)/include -I$(PS1_BUILD_DIR) -I$(NUGGET) -DTA_32BIT
+
+NUGGET_CFLAGS := -O2 $(ARCHFLAGS) $(COMMONFLAGS) -I$(NUGGET)
+
+ENGINE_CFLAGS := -O2 $(ARCHFLAGS) $(COMMONFLAGS) -DTA_32BIT -DTA_DISABLE_BILINEAR_FILTERING \
+ -I$(ROOT_DIR)/include -I$(PS1_BUILD_DIR) \
+ -std=c99 -nostdinc -Wall -Wextra -Werror -pedantic -pedantic-errors
+
+LDFLAGS := -Wl,-Map=$(PS1_MAP) -nostdlib -T$(NUGGET)/nooverlay.ld -T$(NUGGET)/ps-exe.ld -static \
+ -Wl,--gc-sections $(ARCHFLAGS) -Wl,--oformat=$(PS1_OFORMAT)
+
+NUGGET_SRCS := common/crt0/crt0.s common/crt0/memory-s.s common/crt0/memory-c.c common/crt0/clz.c \
+ third_party/arith64/arith64.c
+NUGGET_OBJS := $(addprefix $(OBJ_DIR)/nugget/,$(addsuffix .o,$(basename $(NUGGET_SRCS))))
+PLATFORM_OBJS := $(OBJ_DIR)/ps1.o
+OBJS := $(NUGGET_OBJS) $(PLATFORM_OBJS)
+
+ROOT_INPUTS := $(shell find $(ROOT_DIR)/src -type f -name '*.c') \
+ $(wildcard $(ROOT_DIR)/include/*.h) \
+ $(wildcard $(ROOT_DIR)/include/screens/*.h) \
+ $(ROOT_DIR)/include/ta_config.h.in \
+ $(shell find $(ROOT_DIR)/files -type f) \
+ $(ROOT_DIR)/Makefile
+
+.DEFAULT_GOAL := all
+.PHONY: all clean dump-license patch-license
+
+all: $(TARGET_CUE) $(TARGET_BIN)
+
+$(PS1_LIBRARY): $(ROOT_INPUTS)
+ $(MAKE) -C "$(ROOT_DIR)" CC="$(PS1_CC)" AR="$(PS1_AR)" BITS=32 \
+ BUILD_DIR="$(PS1_BUILD)" TA_DISABLE_BILINEAR_FILTERING=true \
+ CFLAGS="$(ENGINE_CFLAGS)" all
+
+$(PS1_BUILD_DIR)/ta_config.h: $(PS1_LIBRARY)
+
+$(OBJ_DIR)/ps1.o: $(MAKEFILE_DIR)ps1.c $(PS1_LIBRARY) $(wildcard $(ROOT_DIR)/include/*.h)
+ mkdir -p "$(@D)"
+ $(PS1_CC) $(PLATFORM_CFLAGS) -c "$<" -o "$@"
+
+$(OBJ_DIR)/nugget/%.o: $(NUGGET)/%.c
+ mkdir -p "$(@D)"
+ $(PS1_CC) $(NUGGET_CFLAGS) -c "$<" -o "$@"
+
+$(OBJ_DIR)/nugget/%.o: $(NUGGET)/%.s
+ mkdir -p "$(@D)"
+ $(PS1_CC) $(ARCHFLAGS) -I$(NUGGET) -c "$<" -o "$@"
+
+$(PS1_ELF): $(OBJS) $(PS1_LIBRARY)
+ mkdir -p "$(@D)"
+ $(PS1_CC) -o "$@" $(OBJS) $(PS1_LIBRARY) $(LDFLAGS)
+
+$(PS1_EXE): $(PS1_ELF)
+ mkdir -p "$(@D)"
+ $(PS1_OBJCOPY) -O binary "$<" "$@"
+
+$(SYSTEM_CNF): $(MAKEFILE_DIR)Makefile
+ mkdir -p "$(@D)"
+ printf 'BOOT = cdrom:\\PSX.EXE;1\r\nTCB = 4\r\nEVENT = 10\r\nSTACK = 801FFF00\r\n' > "$@"
+
+$(ISO_XML): $(MAKEFILE_DIR)Makefile
+ mkdir -p "$(@D)"
+ { \
+ echo '<?xml version="1.0" encoding="UTF-8"?>'; \
+ echo '<iso_project>'; \
+ echo ' <track type="data">'; \
+ echo ' <identifiers system="PLAYSTATION" application="PLAYSTATION" volume="TUXANCI2" volume_set="TUXANCI2"/>'; \
+ echo ' <directory_tree>'; \
+ echo ' <file name="SYSTEM.CNF" type="data" source="$(SYSTEM_CNF)"/>'; \
+ echo ' <file name="PSX.EXE" type="data" source="$(PS1_EXE)"/>'; \
+ echo ' <dummy sectors="1024"/>'; \
+ echo ' </directory_tree>'; \
+ echo ' </track>'; \
+ echo '</iso_project>'; \
+ } > "$@"
+
+$(TARGET_BIN) $(TARGET_CUE) &: $(PS1_EXE) $(SYSTEM_CNF) $(ISO_XML)
+ $(MKPSXISO) -y -q -o "$(TARGET_BIN)" -c "$(TARGET_CUE)" "$(ISO_XML)"
+ $(PSXLICENSEPATCH) --region $(PS1_REGION) \
+ $(if $(PS1_TEXT),--text "$(PS1_TEXT)") \
+ $(if $(PS1_TMD),--tmd "$(abspath $(PS1_TMD))") \
+ "$(TARGET_BIN)"
+
+patch-license: $(TARGET_BIN)
+ $(PSXLICENSEPATCH) --region $(PS1_REGION) \
+ $(if $(PS1_TEXT),--text "$(PS1_TEXT)") \
+ $(if $(PS1_TMD),--tmd "$(abspath $(PS1_TMD))") \
+ "$(TARGET_BIN)"
+
+clean:
+ $(RM) "$(TARGET_BIN)" "$(TARGET_CUE)"
+ $(MAKE) -C "$(ROOT_DIR)" BUILD_DIR="$(PS1_BUILD)" clean
+ $(RM) -r "$(PS1_BUILD_DIR)"
diff --git a/platforms/ps1/ps1.c b/platforms/ps1/ps1.c
@@ -0,0 +1,444 @@
+#include "ta_api.h"
+#include "common/hardware/hwregs.h"
+#include "common/hardware/irq.h"
+#include "common/hardware/gpu.h"
+
+#define SCREEN_WIDTH 320
+#define SCREEN_HEIGHT 240
+#define WINDOW_BPP 32
+
+#define STACK_RESERVE 0x20000u
+
+#define D2_MADR (*(volatile ta_uint32 *)0xbf8010a0)
+#define D2_BCR (*(volatile ta_uint32 *)0xbf8010a4)
+#define D2_CHCR (*(volatile ta_uint32 *)0xbf8010a8)
+
+#define SIO_DATA (*(volatile ta_uint8 *)0xbf801040)
+#define SIO_STAT (*(volatile ta_uint16 *)0xbf801044)
+#define SIO_MODE (*(volatile ta_uint16 *)0xbf801048)
+#define SIO_CTRL (*(volatile ta_uint16 *)0xbf80104a)
+#define SIO_BAUD (*(volatile ta_uint16 *)0xbf80104e)
+
+#define SIO_STAT_RXRDY 0x0002u
+#define SIO_STAT_IRQ 0x0200u
+#define SIO_CTRL_ACK 0x0010u
+#define SIO_CTRL_RESET 0x0040u
+#define SIO_CTRL_PAD 0x1003u
+
+extern char __bss_end[];
+extern char __ram_top[];
+
+typedef struct heap_block {
+ ta_uint32 size;
+ ta_uint32 reserved;
+ struct heap_block *next;
+} heap_block;
+
+#define HEAP_HEADER 8u
+#define HEAP_MIN_PAYLOAD 8u
+
+static heap_block *heap_free_list;
+
+static void heap_init(void) {
+ ta_uint32 start = ((ta_uint32)__bss_end + 7u) & ~7u;
+ ta_uint32 end = ((ta_uint32)__ram_top - STACK_RESERVE) & ~7u;
+
+ heap_free_list = (heap_block *)start;
+ heap_free_list->size = end - start - HEAP_HEADER;
+ heap_free_list->next = TA_NULL;
+}
+
+void *ta_malloc(ta_size size) {
+ heap_block *previous = TA_NULL;
+ heap_block *block = heap_free_list;
+
+ if (size == 0) {
+ return TA_NULL;
+ }
+
+ size = (size + 7u) & ~7u;
+ if (size < HEAP_MIN_PAYLOAD) {
+ size = HEAP_MIN_PAYLOAD;
+ }
+
+ while (block) {
+ if (block->size >= size) {
+ ta_uint32 remainder = block->size - (ta_uint32)size;
+
+ if (remainder >= HEAP_HEADER + HEAP_MIN_PAYLOAD) {
+ heap_block *rest = (heap_block *)((ta_uint8 *)block + HEAP_HEADER + size);
+
+ rest->size = remainder - HEAP_HEADER;
+ rest->next = block->next;
+ block->size = (ta_uint32)size;
+
+ if (previous) {
+ previous->next = rest;
+ } else {
+ heap_free_list = rest;
+ }
+ } else if (previous) {
+ previous->next = block->next;
+ } else {
+ heap_free_list = block->next;
+ }
+
+ return (ta_uint8 *)block + HEAP_HEADER;
+ }
+
+ previous = block;
+ block = block->next;
+ }
+
+ return TA_NULL;
+}
+
+void ta_free(void *ptr) {
+ heap_block *block;
+ heap_block *previous = TA_NULL;
+ heap_block *next = heap_free_list;
+
+ if (!ptr) {
+ return;
+ }
+
+ block = (heap_block *)((ta_uint8 *)ptr - HEAP_HEADER);
+
+ while (next && next < block) {
+ previous = next;
+ next = next->next;
+ }
+
+ block->next = next;
+
+ if (next && (ta_uint8 *)block + HEAP_HEADER + block->size == (ta_uint8 *)next) {
+ block->size += HEAP_HEADER + next->size;
+ block->next = next->next;
+ }
+
+ if (previous) {
+ if ((ta_uint8 *)previous + HEAP_HEADER + previous->size == (ta_uint8 *)block) {
+ previous->size += HEAP_HEADER + block->size;
+ previous->next = block->next;
+ } else {
+ previous->next = block;
+ }
+ } else {
+ heap_free_list = block;
+ }
+}
+
+void *ta_realloc(void *ptr, ta_size new_size) {
+ heap_block *block;
+ ta_uint8 *fresh;
+ ta_uint8 *source;
+ ta_size copy;
+
+ if (!ptr) {
+ return ta_malloc(new_size);
+ }
+
+ if (new_size == 0) {
+ ta_free(ptr);
+ return TA_NULL;
+ }
+
+ block = (heap_block *)((ta_uint8 *)ptr - HEAP_HEADER);
+ if (block->size >= new_size) {
+ return ptr;
+ }
+
+ fresh = (ta_uint8 *)ta_malloc(new_size);
+ if (!fresh) {
+ return TA_NULL;
+ }
+
+ source = (ta_uint8 *)ptr;
+ copy = block->size;
+ for (ta_size i = 0; i < copy; i++) {
+ fresh[i] = source[i];
+ }
+
+ ta_free(ptr);
+ return fresh;
+}
+
+void ta_exit(int status) {
+ (void)status;
+
+ __asm__ volatile (
+ "mfc0 $t0, $12\n\t"
+ "li $t1, 0xfffffffe\n\t"
+ "and $t0, $t0, $t1\n\t"
+ "mtc0 $t0, $12\n\t"
+ "lui $t0, 0xbfc0\n\t"
+ "jr $t0\n\t"
+ );
+}
+
+#define BUFFER_Y0 0
+#define BUFFER_Y1 SCREEN_HEIGHT
+
+static ta_uint16 framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT] __attribute__((aligned(16)));
+static int back_buffer;
+
+static void disable_interrupts(void) {
+ ta_uint32 status;
+
+ __asm__ volatile("mfc0 %0, $12\n\tnop" : "=r"(status));
+ status &= ~1u;
+ __asm__ volatile("mtc0 %0, $12\n\tnop\n\tnop" : : "r"(status));
+}
+
+static void wait_vblank(void) {
+ while ((IREG & IRQ_VBLANK) == 0) {}
+ IREG = ~(ta_uint32)IRQ_VBLANK;
+}
+
+static ta_bool console_is_pal(void) {
+ return (ta_bool)(*(volatile char *)0xbfc7ff52 == 'E');
+}
+
+static void video_init(void) {
+ const ta_bool pal = console_is_pal();
+ const ta_uint32 vertical_start = pal ? 0x23u : 0x10u;
+ const ta_uint32 vertical_end = vertical_start + SCREEN_HEIGHT;
+ struct FastFill fill;
+
+ sendGPUStatus(0x00000000);
+ sendGPUStatus(0x08000001u | (pal ? 0x08u : 0x00u));
+ sendGPUStatus(0x06000000u | 0x260u | ((0x260u + SCREEN_WIDTH * 8u) << 12));
+ sendGPUStatus(0x07000000u | vertical_start | (vertical_end << 10));
+ sendGPUStatus(generateDisplayArea(0, BUFFER_Y0));
+ sendGPUStatus(0x04000002u);
+
+ fill.c.packed = 0;
+ fill.x = 0;
+ fill.y = 0;
+ fill.w = SCREEN_WIDTH;
+ fill.h = SCREEN_HEIGHT * 2;
+ fastFill(&fill);
+
+ enableDisplay();
+ back_buffer = 1;
+}
+
+static ta_uint16 pack_rgb(ta_uint32 r, ta_uint32 g, ta_uint32 b) {
+ return (ta_uint16)((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
+}
+
+static void compose(void) {
+ const ta_uint32 count = ta_display_sprites ? ta_display_sprites->count : 0;
+
+ ta_uint32 *clear = (ta_uint32 *)framebuffer;
+
+ for (ta_uint32 i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT / 2; i += 8) {
+ clear[i + 0] = 0;
+ clear[i + 1] = 0;
+ clear[i + 2] = 0;
+ clear[i + 3] = 0;
+ clear[i + 4] = 0;
+ clear[i + 5] = 0;
+ clear[i + 6] = 0;
+ clear[i + 7] = 0;
+ }
+
+ for (ta_uint32 s = 0; s < count; s++) {
+ const ta_display_sprite *sprite = ta_display_sprites->sprites[s];
+ const ta_uint32 *source;
+ ta_uint32 width;
+ ta_uint32 height;
+
+ if (!sprite || sprite->format != TA_DISPLAY_PIXEL_FORMAT_32 ||
+ sprite->x >= SCREEN_WIDTH || sprite->y >= SCREEN_HEIGHT) {
+ continue;
+ }
+
+ source = (const ta_uint32 *)sprite->pixels;
+ width = sprite->width;
+ height = sprite->height;
+
+ for (ta_uint32 y = 0; y < height && sprite->y + y < SCREEN_HEIGHT; y++) {
+ const ta_uint32 *row = source + y * width;
+ ta_uint16 *destination = framebuffer + (sprite->y + y) * SCREEN_WIDTH + sprite->x;
+
+ for (ta_uint32 x = 0; x < width && sprite->x + x < SCREEN_WIDTH; x++) {
+ const ta_uint32 pixel = row[x];
+ const ta_uint32 alpha = pixel & 0xffu;
+
+ if (alpha == 0) {
+ continue;
+ }
+
+ if (alpha == 0xffu) {
+ destination[x] = pack_rgb(pixel >> 24, (pixel >> 16) & 0xffu, (pixel >> 8) & 0xffu);
+ } else {
+ const ta_uint16 below = destination[x];
+ const ta_int32 dr = below & 0x1f;
+ const ta_int32 dg = (below >> 5) & 0x1f;
+ const ta_int32 db = (below >> 10) & 0x1f;
+ const ta_int32 sr = (ta_int32)(pixel >> 27);
+ const ta_int32 sg = (ta_int32)((pixel >> 19) & 0x1f);
+ const ta_int32 sb = (ta_int32)((pixel >> 11) & 0x1f);
+ const ta_int32 a = (ta_int32)alpha;
+
+ destination[x] = (ta_uint16)(
+ (dr + (((sr - dr) * a + 128) >> 8)) |
+ ((dg + (((sg - dg) * a + 128) >> 8)) << 5) |
+ ((db + (((sb - db) * a + 128) >> 8)) << 10));
+ }
+ }
+ }
+ }
+}
+
+static void upload(int buffer) {
+ const ta_uint32 y = buffer ? BUFFER_Y1 : BUFFER_Y0;
+ const ta_uint32 words = SCREEN_WIDTH * SCREEN_HEIGHT / 2;
+
+ waitGPU();
+ GPU_DATA = 0xa0000000u;
+ GPU_DATA = 0u | (y << 16);
+ GPU_DATA = SCREEN_WIDTH | ((ta_uint32)SCREEN_HEIGHT << 16);
+
+ DPCR |= 0x800u;
+ D2_MADR = (ta_uint32)framebuffer & 0x1fffffffu;
+ D2_BCR = ((words / 16u) << 16) | 16u;
+ D2_CHCR = 0x01000201u;
+
+ while (D2_CHCR & 0x01000000u) {}
+}
+
+static void flip(int buffer) {
+ sendGPUStatus(generateDisplayArea(0, buffer ? BUFFER_Y1 : BUFFER_Y0));
+}
+
+static ta_bool pad_exchange(ta_uint8 out, ta_uint8 *in, ta_bool wait_ack) {
+ ta_uint32 timeout = 0x4000;
+
+ SIO_DATA = out;
+
+ while (!(SIO_STAT & SIO_STAT_RXRDY)) {
+ if (--timeout == 0) {
+ return ta_false;
+ }
+ }
+
+ *in = SIO_DATA;
+
+ if (wait_ack) {
+ timeout = 0x400;
+
+ while (!(SIO_STAT & SIO_STAT_IRQ)) {
+ if (--timeout == 0) {
+ return ta_false;
+ }
+ }
+
+ SIO_CTRL = SIO_CTRL_PAD | SIO_CTRL_ACK;
+ }
+
+ return ta_true;
+}
+
+static ta_bool pad_read(ta_uint16 *buttons) {
+ static const ta_uint8 request[5] = {
+ 0x01,
+ 0x42,
+ 0x00,
+ 0x00,
+ 0x00
+ };
+
+ ta_uint8 reply[5] = {
+ 0xff,
+ 0xff,
+ 0xff,
+ 0xff,
+ 0xff
+ };
+
+ ta_bool ok = ta_true;
+
+ SIO_CTRL = SIO_CTRL_PAD;
+
+ for (ta_uint32 i = 0; i < 5 && ok; i++) {
+ ok = pad_exchange(request[i], &reply[i], (ta_bool)(i < 4));
+ }
+
+ SIO_CTRL = 0;
+
+ if (!ok || reply[2] != 0x5a) {
+ return ta_false;
+ }
+
+ *buttons = (ta_uint16)(reply[3] | ((ta_uint16)reply[4] << 8));
+ return ta_true;
+}
+
+static void pad_init(void) {
+ SIO_CTRL = SIO_CTRL_RESET;
+ SIO_MODE = 0x000d;
+ SIO_BAUD = 0x0088;
+ SIO_CTRL = 0;
+}
+
+static void set_key(ta_keyboard_keys key, ta_bool down) {
+ if (down) {
+ ta_keyboard_key_down(key);
+ } else {
+ ta_keyboard_key_up(key);
+ }
+}
+
+static void update_controller_input(void) {
+ ta_uint16 buttons;
+
+ if (!pad_read(&buttons)) {
+ ta_keyboard_clear();
+ return;
+ }
+
+ buttons = (ta_uint16)~buttons;
+
+ set_key(KEY_UP, (ta_bool)((buttons & 0x0010u) != 0));
+ set_key(KEY_DOWN, (ta_bool)((buttons & 0x0040u) != 0));
+ set_key(KEY_LEFT, (ta_bool)((buttons & 0x0080u) != 0));
+ set_key(KEY_RIGHT, (ta_bool)((buttons & 0x0020u) != 0));
+ set_key(KEY_ENTER, (ta_bool)((buttons & 0x0008u) != 0));
+}
+
+int main(void) {
+ disable_interrupts();
+ heap_init();
+ video_init();
+ pad_init();
+
+ ta_display->width = SCREEN_WIDTH;
+ ta_display->height = SCREEN_HEIGHT;
+ ta_display->bpp = WINDOW_BPP;
+ ta_display->sprite_mode = ta_true;
+
+ ta_main_init();
+
+ for (;;) {
+ ta_bool redraw;
+
+ update_controller_input();
+ redraw = ta_main_update();
+
+ if (redraw) {
+ compose();
+ upload(back_buffer);
+ }
+
+ wait_vblank();
+
+ if (redraw) {
+ flip(back_buffer);
+ back_buffer ^= 1;
+ }
+ }
+
+ return 0;
+}