M Makefile => Makefile +16 -36
@@ 1,26 1,29 @@
-.PHONY: sim format
-
HEAP_SIZE = 8388208
STACK_SIZE = 61800
PRODUCT = Varvara.pdx
# Locate the SDK
-ifeq (,$(PLAYDATE_SDK))
-$(error Please set the PLAYDATE_SDK environment variable to your SDK directory)
-endif
+# ~/Documents/playdate/PlaydateSDK-1.12.3
+SDK = ${PLAYDATE_SDK_PATH}
-SDK := $(PLAYDATE_SDK)
+ifeq ($(SDK),)
+ SDK = $(shell egrep '^\s*SDKRoot' ~/.Playdate/config | head -n 1 | cut -c9-)
+endif
-# Download Uxn
-ifeq (,$(wildcard uxn))
-$(shell git clone https://git.sr.ht/~rabbits/uxn)
+ifeq ($(SDK),)
+$(error SDK path not found; set ENV value PLAYDATE_SDK_PATH)
endif
-VPATH += uxn/src devices
+######
+# IMPORTANT: You must add your source folders to VPATH for make to find them
+# ex: VPATH += src1:src2
+######
+
+VPATH += src src/devices
# List C source files here
-SRC = main.c uxn/src/uxn-fast.c devices/apu.c devices/ppu.c
+SRC = src/main.c src/uxn.c src/devices/apu.c src/devices/ppu.c
# List all user directories here
UINCDIR =
@@ 40,31 43,8 @@ ULIBDIR =
# List all user libraries here
ULIBS =
-include $(PLAYDATE_SDK)/C_API/buildsupport/common.mk
-
-TARGET_ROM_SRC ?= uxn/projects/examples/devices/screen.tal
-.PHONY: $(TARGET_ROM_SRC)
-TARGET_ROM ?= boot.rom
-
-all sim: $(PRODUCT)/$(TARGET_ROM)
-
-uxn/src/uxn-fast.c uxn/src/uxnasm.c: uxn/build.sh
-
-build/uxnasm: uxn/src/uxnasm.c
- mkdir -p $(dir $@)
- cc $(CFLAGS) $^ -o $@
-
-$(PRODUCT)/$(TARGET_ROM): build/uxnasm $(TARGET_ROM_SRC)
- mkdir -p $(dir $@)
- $^ $@
+include $(SDK)/C_API/buildsupport/common.mk
sim: all
- $(PLAYDATE_SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))
-
-format:
- clang-format -i main.c
- clang-format -i devices/apu.h
- clang-format -i devices/apu.c
- clang-format -i devices/ppu.h
- clang-format -i devices/ppu.c
+ $(SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))
A etc/controller.rom => etc/controller.rom +0 -0
A etc/controller.tal => etc/controller.tal +169 -0
@@ 0,0 1,169 @@
+( Controller:
+ Buttons should highlight on press and display the button and key bytes. )
+
+|00 @System &vector $2 &wst $1 &rst $1 &pad $4 &r $2 &g $2 &b $2 &debug $1 &halt $1
+|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1
+|80 @Controller &vector $2 &button $1 &key $1
+
+|0000
+
+@center
+ &x $2 &y $2
+@frame
+ &w $2 &h $2 &x0 $2 &y0 $2 &x1 $2 &y1 $2
+
+|0100 ( -> )
+
+ ( theme )
+ #0fff .System/r DEO2
+ #0f0f .System/g DEO2
+ #0f0f .System/b DEO2
+ ( find center )
+ .Screen/width DEI2 #01 SFT2 .center/x STZ2
+ .Screen/height DEI2 #01 SFT2 .center/y STZ2
+ ( place controller )
+ #0068 .frame/w STZ2
+ #0030 .frame/h STZ2
+ .center/x LDZ2 .frame/w LDZ2 #01 SFT2 SUB2 .frame/x0 STZ2
+ .center/y LDZ2 .frame/h LDZ2 #01 SFT2 SUB2 .frame/y0 STZ2
+ .frame/x0 LDZ2 .frame/w LDZ2 ADD2 .frame/x1 STZ2
+ .frame/y0 LDZ2 .frame/h LDZ2 ADD2 .frame/y1 STZ2
+ ( vectors )
+ ;on-button .Controller/vector DEO2
+ ( frame )
+ .frame/x0 LDZ2 .frame/y0 LDZ2
+ .frame/x1 LDZ2 .frame/y1 LDZ2
+ #03 ;line-rect JSR2
+
+ ,draw-controller JSR
+
+BRK
+
+@on-button ( -> )
+
+ ,draw-controller JSR
+
+ ( print stack on start button )
+ .Controller/button DEI #08 EQU [ JMP BRK ] #010e DEO
+
+BRK
+
+@draw-controller ( -- )
+
+ .Controller/button DEI STH
+
+ ( d-pad )
+ .frame/x0 LDZ2 #0010 ADD2 .Screen/x DEO2
+ .frame/y0 LDZ2 #0010 ADD2 .Screen/y DEO2
+ ;controller-icn/dpad-up .Screen/addr DEO2
+ #03 STHkr #04 SFT #01 AND SUB .Screen/sprite DEO
+ .Screen/y DEI2 #0010 ADD2 .Screen/y DEO2
+ ;controller-icn/dpad-down .Screen/addr DEO2
+ #03 STHkr #05 SFT #01 AND SUB .Screen/sprite DEO
+ .Screen/y DEI2 #0008 SUB2 .Screen/y DEO2
+ .Screen/x DEI2 #0008 SUB2 .Screen/x DEO2
+ ;controller-icn/dpad-left .Screen/addr DEO2
+ #03 STHkr #06 SFT #01 AND SUB .Screen/sprite DEO
+ .Screen/x DEI2 #0010 ADD2 .Screen/x DEO2
+ ;controller-icn/dpad-right .Screen/addr DEO2
+ #03 STHkr #07 SFT #01 AND SUB .Screen/sprite DEO
+ .Screen/x DEI2 #0008 SUB2 .Screen/x DEO2
+ ;controller-icn/dpad .Screen/addr DEO2
+ #03 .Screen/sprite DEO
+
+ ( options )
+ .center/y LDZ2 #0009 ADD2 .Screen/y DEO2
+ .center/x LDZ2 #0009 SUB2 .Screen/x DEO2
+ ;controller-icn/option .Screen/addr DEO2
+ #03 STHkr #03 SFT #01 AND SUB .Screen/sprite DEO
+ .center/x LDZ2 #0004 ADD2 .Screen/x DEO2
+ ;controller-icn/option .Screen/addr DEO2
+ #03 STHkr #02 SFT #01 AND SUB .Screen/sprite DEO
+
+ ( buttons )
+ .center/y LDZ2 .Screen/y DEO2
+ .center/x LDZ2 #0018 ADD2 .Screen/x DEO2
+ ;controller-icn/button .Screen/addr DEO2
+ #03 STHkr #01 SFT #01 AND SUB .Screen/sprite DEO
+ .Screen/y DEI2 #000a ADD2 .Screen/y DEO2
+ ;font-hex/b .Screen/addr DEO2
+ #03 .Screen/sprite DEO
+
+ .center/y LDZ2 .Screen/y DEO2
+ .center/x LDZ2 #0024 ADD2 .Screen/x DEO2
+ ;controller-icn/button .Screen/addr DEO2
+ #03 STHr #01 AND SUB .Screen/sprite DEO
+ .Screen/y DEI2 #000a ADD2 .Screen/y DEO2
+ ;font-hex/a .Screen/addr DEO2
+ #03 .Screen/sprite DEO
+
+ .center/x LDZ2 #0010 SUB2 .Screen/x DEO2
+ .center/y LDZ2 #0010 SUB2 .Screen/y DEO2
+ #01 .Screen/auto DEO
+ .Controller/button DEI2 ,draw-short JSR
+ #00 .Screen/auto DEO
+
+JMP2r
+
+( generics )
+
+@draw-short ( short* -- )
+
+ SWP ,draw-byte JSR
+
+@draw-byte ( byte -- )
+
+ DUP #04 SFT ,draw-hex JSR
+
+@draw-hex ( char -- )
+
+ #00 SWP #0f AND #30 SFT2 ;font-hex ADD2 .Screen/addr DEO2
+ #03 .Screen/sprite DEO
+
+JMP2r
+
+@line-rect ( x1* y1* x2* y2* color -- )
+
+ STH
+ DUP2 ,&ver-y2 STR2 ,&hor-y2 STR2
+ DUP2 ,&ver-x2 STR2 ,&hor-x2 STR2
+ DUP2 ,&ver-y1 STR2 ,&hor-y1 STR2
+ DUP2 ,&ver-x1 STR2 ,&hor-x1 STR2
+ ( horizontal )
+ [ LIT2 &hor-x2 $2 ] INC2 [ LIT2 &hor-x1 $2 ]
+ &hor
+ DUP2 .Screen/x DEO2
+ [ LIT2 &hor-y1 $2 ] .Screen/y DEO2 STHkr .Screen/pixel DEOk
+ [ LIT2 &hor-y2 $2 ] .Screen/y DEO2 DEO
+ INC2 GTH2k ,&hor JCN
+ POP2 POP2
+ ( vertical )
+ [ LIT2 &ver-y2 $2 ] [ LIT2 &ver-y1 $2 ]
+ &ver
+ DUP2 .Screen/y DEO2
+ [ LIT2 &ver-x1 $2 ] .Screen/x DEO2 STHkr .Screen/pixel DEOk
+ [ LIT2 &ver-x2 $2 ] .Screen/x DEO2 DEO
+ INC2 GTH2k ,&ver JCN
+ POP2 POP2
+ POPr
+
+JMP2r
+
+@controller-icn
+ &dpad ffff ffff ffff ffff
+ &dpad-up 7eff e7c3 ffff ffff
+ &dpad-down ffff ffff c3e7 ff7e
+ &dpad-left 7fff efcf cfef ff7f
+ &dpad-right feff f7f3 f3f7 fffe
+ &option 0000 7eff ff7e 0000
+ &button 3c7e ffff ffff 7e3c
+
+@font-hex
+ 007c 8282 8282 827c 0030 1010 1010 1010
+ 007c 8202 7c80 80fe 007c 8202 1c02 827c
+ 000c 1424 4484 fe04 00fe 8080 7c02 827c
+ 007c 8280 fc82 827c 00fe 0202 0408 1010
+ 007c 8282 7c82 827c 007c 8282 7e02 827c
+ &a 007c 8202 7e82 827e &b 00fc 8282 fc82 82fc
+ 007c 8280 8080 827c 00fc 8282 8282 82fc
+ 00fe 8080 fe80 80fe 00fe 8080 f080 8080
A old/Makefile => old/Makefile +70 -0
@@ 0,0 1,70 @@
+.PHONY: sim format
+
+HEAP_SIZE = 8388208
+STACK_SIZE = 61800
+
+PRODUCT = Varvara.pdx
+
+# Locate the SDK
+ifeq (,$(PLAYDATE_SDK))
+$(error Please set the PLAYDATE_SDK environment variable to your SDK directory)
+endif
+
+SDK := $(PLAYDATE_SDK)
+
+# Download Uxn
+ifeq (,$(wildcard uxn))
+$(shell git clone https://git.sr.ht/~rabbits/uxn)
+endif
+
+VPATH += uxn/src devices
+
+# List C source files here
+SRC = main.c uxn/src/uxn.c devices/apu.c devices/ppu.c
+
+# List all user directories here
+UINCDIR =
+
+# List user asm files
+UASRC =
+
+# List all user C define here, like -D_DEBUG=1
+UDEFS =
+
+# Define ASM defines here
+UADEFS =
+
+# List the user directory to look for the libraries here
+ULIBDIR =
+
+# List all user libraries here
+ULIBS =
+
+include $(PLAYDATE_SDK)/C_API/buildsupport/common.mk
+
+TARGET_ROM_SRC ?= uxn/projects/examples/devices/screen.tal
+.PHONY: $(TARGET_ROM_SRC)
+TARGET_ROM ?= boot.rom
+
+all sim: $(PRODUCT)/$(TARGET_ROM)
+
+uxn/src/uxn.c uxn/src/uxnasm.c: uxn/build.sh
+
+build/uxnasm: uxn/src/uxnasm.c
+ mkdir -p $(dir $@)
+ cc $(CFLAGS) $^ -o $@
+
+$(PRODUCT)/$(TARGET_ROM): build/uxnasm $(TARGET_ROM_SRC)
+ mkdir -p $(dir $@)
+ $^ $@
+
+sim: all
+ $(PLAYDATE_SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))
+
+format:
+ clang-format -i main.c
+ clang-format -i devices/apu.h
+ clang-format -i devices/apu.c
+ clang-format -i devices/ppu.h
+ clang-format -i devices/ppu.c
+
A old/main.c => old/main.c +415 -0
@@ 0,0 1,415 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#include "pd_api.h"
+#include "uxn/src/uxn.h"
+#include "devices/apu.h"
+#include "devices/ppu.h"
+
+#define POLYPHONY 4
+#define BOOT_ROM_FILENAME "boot.rom"
+#define SNAPSHOT_FILENAME "uxn.sna"
+#define SNAPSHOT_VERSION 1
+
+static PlaydateAPI *pd = NULL;
+static SoundSource *apu_sources[POLYPHONY];
+static Uxn u;
+static Device *devctrl, *devconsole, *devscreen, *devmouse, *devdatetime, *devaudio0;
+static Ppu ppu;
+static Apu apu[POLYPHONY];
+
+static const char *errors[] = {
+ "Working-stack underflow",
+ "Return-stack underflow",
+ "Working-stack overflow",
+ "Return-stack overflow",
+ "Working-stack division by zero",
+ "Return-stack division by zero",
+ "Execution timeout"};
+
+int
+uxn_halt(Uxn *u, Uint8 error, Uint16 addr)
+{
+ pd->system->error("Halted: %s#%04x, at 0x%04x\n", errors[error], u->ram[addr], addr);
+ return 0;
+}
+
+static void
+docolors(Device *d)
+{
+ int i, sum;
+ for(i = 0; i < 4; ++i) {
+ sum = ((d->dat[0x8 + i / 2] >> (!(i % 2) << 2)) & 0x0f);
+ sum += ((d->dat[0xa + i / 2] >> (!(i % 2) << 2)) & 0x0f);
+ sum += ((d->dat[0xc + i / 2] >> (!(i % 2) << 2)) & 0x0f);
+ ppu.color_is_white[i] = sum > 0x17;
+ }
+ ppu_clear(&ppu);
+ render_all(&ppu);
+}
+
+static uint8_t
+getcolors(int i)
+{
+ return (ppu.color_is_white[i] ? 0xf0 : 0) + (ppu.color_is_white[i + 1] ? 0x0f : 0);
+}
+
+static int
+system_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ if(!w) { /* read */
+ switch(b0) {
+ case 0x8 ... 0xd: d->dat[b0] = getcolors((b0 & 1) << 1); break;
+ }
+ } else { /* write */
+ switch(b0) {
+ case 0x8 ... 0xd: docolors(d); break;
+ }
+ }
+ return 1;
+}
+
+static int
+console_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ if(w && b0 > 0x7)
+ pd->system->logToConsole("%c", d->dat[b0]);
+ return 1;
+}
+
+static int
+screen_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ /*
+ if(w && b0 == 0xe) {
+ Uint16 x = peek16(d->dat, 0x8);
+ Uint16 y = peek16(d->dat, 0xa);
+ Uint8 layer = d->dat[0xe] & 0x40;
+ ppu_pixel(&ppu, !!layer, x, y, d->dat[0xe] & 0x3);
+ if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 1);
+ if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 1);
+ pd->graphics->markUpdatedRows(y, y);
+ } else if(w && b0 == 0xf) {
+ Uint16 x = peek16(d->dat, 0x8);
+ Uint16 y = peek16(d->dat, 0xa);
+ Uint8 layer = d->dat[0xf] & 0x40;
+ Uint8 *addr = &d->mem[peek16(d->dat, 0xc)];
+ if(d->dat[0xf] & 0x80) {
+ ppu_2bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
+ if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 16);
+ } else {
+ ppu_1bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
+ if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8);
+ }
+ if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 8);
+ if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 8);
+ pd->graphics->markUpdatedRows(y, y + 7);
+ }
+ */
+ return 1;
+}
+
+static int
+file_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ /*
+ Uint8 read = b0 == 0xd;
+ if(w && (read || b0 == 0xf)) {
+ char *name = (char *)&d->mem[peek16(d->dat, 0x8)];
+ Uint16 result = 0, length = peek16(d->dat, 0xa);
+ Uint16 offset = peek16(d->dat, 0x4);
+ Uint16 addr = peek16(d->dat, b0 - 1);
+ SDFile *f = pd->file->open(name, read ? (kFileRead | kFileReadData) : (offset ? kFileAppend : kFileWrite));
+ if(f) {
+ pd->system->logToConsole("%s %s %s #%04x, ", read ? "Loading" : "Saving", name, read ? "to" : "from", addr);
+ if(pd->file->seek(f, offset, SEEK_SET) != -1)
+ result = read ? pd->file->read(f, &d->mem[addr], length) : pd->file->write(f, &d->mem[addr], length);
+ pd->system->logToConsole("%04x bytes\n", result);
+ pd->file->close(f);
+ } else {
+ pd->system->logToConsole("unable to open %s", name);
+ }
+ poke16(d->dat, 0x2, result);
+ }
+ */
+ return 1;
+}
+
+static int
+audio_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ Apu *c = &apu[d - devaudio0];
+ if(!w) {
+ if(b0 == 0x2)
+ d->dat[0x2] = 0x0;
+ /* poke16(d->dat, 0x2, c->i); */
+ else if(b0 == 0x4)
+ d->dat[0x4] = apu_get_vu(c);
+ } else if(b0 == 0xf) {
+ c->len = PEEK16(d->dat, 0xa);
+ c->addr = PEEK16(d->dat, 0xc);
+ c->volume[0] = d->dat[0xe] >> 4;
+ c->volume[1] = d->dat[0xe] & 0xf;
+ c->repeat = !(d->dat[0xf] & 0x80);
+ apu_start(c, PEEK16(d->dat, 0x8), d->dat[0xf] & 0x7f);
+ }
+ return 1;
+}
+
+static int
+nil_talk(Device *d, Uint8 b0, Uint8 w)
+{
+ (void)d;
+ (void)b0;
+ (void)w;
+ return 1;
+}
+
+static int
+update_datetime(lua_State *L)
+{
+ // DateTime
+ int i;
+ for(i = 0; i < 11; ++i)
+ devdatetime->dat[i] = pd->lua->getArgInt(i + 1);
+ return 0;
+}
+
+static int
+update_controller(lua_State *L)
+{
+ // Buttons
+ PDButtons state, pushed, released;
+ pd->system->getButtonState(&state, &pushed, &released);
+ if(pushed || released) {
+ devctrl->dat[2] = 0x00;
+ if(state & kButtonA)
+ devctrl->dat[2] |= 0x01;
+ if(state & kButtonB)
+ devctrl->dat[2] |= 0x02;
+ if(state & kButtonUp)
+ devctrl->dat[2] |= 0x10;
+ if(state & kButtonDown)
+ devctrl->dat[2] |= 0x20;
+ if(state & kButtonLeft)
+ devctrl->dat[2] |= 0x40;
+ if(state & kButtonRight)
+ devctrl->dat[2] |= 0x80;
+ uxn_eval(&u, PEEK16(devctrl->dat, 0));
+ }
+
+ // Crank
+ unsigned char crank = pd->system->getCrankChange();
+ if(crank) {
+ devmouse->dat[7] = crank;
+ uxn_eval(&u, PEEK16(devmouse->dat, 0));
+ devmouse->dat[7] = 0x00;
+ }
+
+ return 0;
+}
+
+static int
+update_mouse(lua_State *L)
+{
+ int i;
+ for(i = 0; i < 5; ++i) {
+ devmouse->dat[i + 2] = pd->lua->getArgInt(i + 1);
+ }
+ uxn_eval(&u, PEEK16(devmouse->dat, 0));
+ return 0;
+}
+
+static int
+update_screen(lua_State *L)
+{
+ uxn_eval(&u, PEEK16(devscreen->dat, 0));
+ return 0;
+}
+
+static int
+audio_callback(void *context, int16_t *left, int16_t *right, int len)
+{
+ return apu_render((Apu *)context, u.ram, left, right, len);
+}
+
+static int
+varvara_init_once(void)
+{
+ int i;
+ SoundChannel *ch = pd->sound->getDefaultChannel();
+ for(i = 0; i < POLYPHONY; ++i) {
+ apu_sources[i] = pd->sound->channel->addCallbackSource(ch, audio_callback, &apu[i], 1);
+ }
+ pd->sound->channel->setVolume(ch, 1.0);
+ pd->sound->channel->setPan(ch, 0.0);
+ return 1;
+}
+
+static int
+post_load_init(void)
+{
+ if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
+ return 0;
+ render_all(&ppu);
+/*
+ uxn_port(&u, 0x0, system_talk);
+ devconsole = uxn_port(&u, 0x1, console_talk);
+ devscreen = uxn_port(&u, 0x2, screen_talk);
+ devaudio0 = uxn_port(&u, 0x3, audio_talk);
+ uxn_port(&u, 0x4, audio_talk);
+ uxn_port(&u, 0x5, audio_talk);
+ uxn_port(&u, 0x6, audio_talk);
+ uxn_port(&u, 0x7, nil_talk);
+ devctrl = uxn_port(&u, 0x8, nil_talk);
+ devmouse = uxn_port(&u, 0x9, nil_talk);
+ uxn_port(&u, 0xa, file_talk);
+ devdatetime = uxn_port(&u, 0xb, nil_talk);
+ uxn_port(&u, 0xc, nil_talk);
+ uxn_port(&u, 0xd, nil_talk);
+ uxn_port(&u, 0xe, nil_talk);
+ uxn_port(&u, 0xf, nil_talk);
+*/
+ return 1;
+}
+
+static int
+load(Uxn *u, char *filepath)
+{
+ SDFile *f = pd->file->open(filepath, kFileRead | kFileReadData);
+ if(f == NULL) {
+ pd->system->error("Failed to open rom.");
+ return 0;
+ }
+ pd->file->read(f, u->ram + PAGE_PROGRAM, sizeof(u->ram) - PAGE_PROGRAM);
+ pd->system->logToConsole("Loaded %s\n", filepath);
+ return post_load_init();
+}
+
+static int
+reset(Uxn *u, char *filepath)
+{
+ free(u->ram);
+ if(!uxn_boot(u, calloc(0x10000, 1))) {
+ pd->system->error("Failed to start uxn.");
+ return 0;
+ }
+ memset(&ppu, 0, sizeof(ppu));
+ memset(&apu, 0, sizeof(apu));
+ if(!load(u, filepath))
+ return 0;
+
+ uxn_eval(u, PAGE_PROGRAM);
+ return 1;
+}
+
+static int
+menu_reset(lua_State *L)
+{
+ if(!reset(&u, BOOT_ROM_FILENAME))
+ pd->system->error("Failed to reset Uxn.");
+ return 0;
+}
+
+static int
+freeze(void)
+{
+ SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileWrite);
+ if(f == NULL) {
+ pd->system->error("Failed to open snapshot for writing.");
+ return 0;
+ }
+ int version = SNAPSHOT_VERSION;
+ /* clang-format off */
+ if(pd->file->write(f, &version, sizeof(version)) != sizeof(version)
+ || pd->file->write(f, &u, sizeof(u)) != sizeof(u)
+ || pd->file->write(f, &ppu, sizeof(ppu)) != sizeof(ppu)
+ || pd->file->write(f, &apu, sizeof(apu)) != sizeof(apu)) {
+ pd->system->error("Failed to write snapshot data.");
+ pd->file->close(f);
+ return 0;
+ }
+ pd->file->close(f);
+ /* clang-format on */
+ return 1;
+}
+
+static int
+thaw(void)
+{
+ SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileRead | kFileReadData);
+ if(f == NULL)
+ return 0;
+ int version;
+ /* clang-format off */
+ if(pd->file->read(f, &version, sizeof(version)) != sizeof(version)
+ || pd->file->read(f, &u, sizeof(u)) != sizeof(u)
+ || pd->file->read(f, &ppu, sizeof(ppu)) != sizeof(ppu)
+ || pd->file->read(f, &apu, sizeof(apu)) != sizeof(apu)) {
+ pd->file->close(f);
+ return 0;
+ }
+ pd->file->close(f);
+ /* clang-format on */
+ if(version != SNAPSHOT_VERSION)
+ return 0;
+ return post_load_init();
+}
+
+struct lua_function_pair {
+ lua_CFunction f;
+ const char *name;
+};
+
+static struct lua_function_pair fns[] = {
+ {update_datetime, "varvaraDateTime"},
+ {update_controller, "varvaraController"},
+ {update_mouse, "varvaraMouse"},
+ {update_screen, "varvaraScreen"},
+ {menu_reset, "varvaraReset"}};
+
+int
+eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg)
+{
+ switch(event) {
+ case kEventInit:
+ pd = playdate;
+ pd->display->setRefreshRate(60);
+
+ if(!varvara_init_once())
+ pd->system->error("Failed to initialize Varvara.");
+
+ if(!thaw() && !reset(&u, BOOT_ROM_FILENAME))
+ pd->system->error("Failed to reset Uxn.");
+
+ break;
+
+ case kEventInitLua: {
+ const char *err;
+ int i;
+ for(i = 0; i < sizeof(fns) / sizeof(struct lua_function_pair); ++i) {
+ if(!pd->lua->addFunction(fns[i].f, fns[i].name, &err))
+ pd->system->error("Failed to create function: %s", err);
+ }
+ break;
+ }
+
+ case kEventTerminate:
+ case kEventLowPower:
+ freeze();
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
A src/devices/apu.c => src/devices/apu.c +95 -0
@@ 0,0 1,95 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#include "apu.h"
+
+#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
+#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
+
+/* clang-format off */
+
+static uint32_t advances[12] = {
+ 0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
+ 0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
+};
+
+/* clang-format on */
+
+static int32_t
+envelope(Apu *c, uint32_t age)
+{
+ if(!c->r) return 0x0888;
+ if(age < c->a) return 0x0888 * age / c->a;
+ if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
+ if(age < c->s) return 0x0444;
+ if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
+ c->advance = 0;
+ return 0x0000;
+}
+
+int
+apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len)
+{
+ int32_t s;
+ if(!c->advance || !c->period) return 0;
+ while(len--) {
+ c->count += c->advance;
+ c->i += c->count / c->period;
+ c->count %= c->period;
+ if(c->i >= c->len) {
+ if(!c->repeat) {
+ c->advance = 0;
+ break;
+ }
+ c->i %= c->len;
+ }
+ s = (int8_t)(dat[c->addr + c->i] + 0x80) * envelope(c, c->age++);
+ *left++ = s * c->volume[0] / 0x180;
+ *right++ = s * c->volume[1] / 0x180;
+ }
+ return 1;
+}
+
+void
+apu_start(Apu *c, uint16_t adsr, uint8_t pitch)
+{
+ if(pitch < 108 && c->len)
+ c->advance = advances[pitch % 12] >> (8 - pitch / 12);
+ else {
+ c->advance = 0;
+ return;
+ }
+ c->a = ADSR_STEP * (adsr >> 12);
+ c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
+ c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
+ c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
+ c->age = 0;
+ c->i = 0;
+ if(c->len <= 0x100) /* single cycle mode */
+ c->period = NOTE_PERIOD * 337 / 2 / c->len;
+ else /* sample repeat mode */
+ c->period = NOTE_PERIOD;
+}
+
+uint8_t
+apu_get_vu(Apu *c)
+{
+ int i;
+ int32_t sum[2] = {0, 0};
+ if(!c->advance || !c->period) return 0;
+ for(i = 0; i < 2; ++i) {
+ if(!c->volume[i]) continue;
+ sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
+ if(sum[i] > 0xf) sum[i] = 0xf;
+ }
+ return (sum[0] << 4) | sum[1];
+}
A src/devices/apu.h => src/devices/apu.h +27 -0
@@ 0,0 1,27 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#include <stdint.h>
+
+#define SAMPLE_FREQUENCY 44100
+
+typedef struct {
+ uint32_t count, advance, period, age, a, d, s, r;
+ uint16_t addr, i, len;
+ int8_t volume[2];
+ uint8_t pitch, repeat;
+} Apu;
+
+int apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len);
+void apu_start(Apu *c, uint16_t adsr, uint8_t pitch);
+uint8_t apu_get_vu(Apu *c);
+void apu_finished_handler(Apu *c);
A src/devices/ppu.c => src/devices/ppu.c +109 -0
@@ 0,0 1,109 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#include "ppu.h"
+
+int
+ppu_init(Ppu *p, uint32_t *framebuffer)
+{
+ p->framebuffer = framebuffer;
+ return 1;
+}
+
+static inline void
+render(Ppu *p, int offset, int count)
+{
+ uint32_t *in = &p->pixels[offset * 3];
+ uint32_t *out = &p->framebuffer[offset];
+ while(count--) {
+ *(out++) = in[0] ^ (in[2] & (in[0] ^ in[1]));
+ in += 3;
+ }
+}
+
+void
+render_all(Ppu *p)
+{
+ render(p, 0, STRIDE_WORDS * HEIGHT);
+}
+
+void
+ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color)
+{
+ if(x >= WIDTH_PIXELS || y >= HEIGHT) return;
+ int offset = y * STRIDE_WORDS + x / 32;
+ uint32_t *px = &p->pixels[offset * 3 + layer];
+ uint32_t mask = 1 << ((x & 0x18) + (7 - (x & 0x7)));
+ if(color & 0x01)
+ p->pixels[offset * 3 + layer] |= mask;
+ else
+ p->pixels[offset * 3 + layer] &= ~mask;
+ if(layer) {
+ if(color)
+ p->pixels[offset * 3 + 2] |= mask;
+ else
+ p->pixels[offset * 3 + 2] &= ~mask;
+ }
+ render(p, offset, 1);
+}
+
+void
+ppu_clear(Ppu *p)
+{
+ uint32_t background = p->color_is_white[0] ? 0xffffffff : 0;
+ int i;
+ for(i = 0; i < HEIGHT * STRIDE_WORDS * 3; i += 3) {
+ p->pixels[i] = background;
+ p->pixels[i + 2] = 0;
+ }
+}
+
+static uint8_t blending[5][16] = {
+ {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
+ {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
+ {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
+ {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
+ {1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};
+
+void
+ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
+{
+ uint16_t v, h;
+ for(v = 0; v < 8; v++)
+ for(h = 0; h < 8; h++) {
+ uint8_t ch1 = (sprite[v] >> (7 - h)) & 0x1;
+ if(ch1 || blending[4][color])
+ ppu_pixel(p,
+ layer,
+ x + (flipx ? 7 - h : h),
+ y + (flipy ? 7 - v : v),
+ blending[ch1][color]);
+ }
+}
+
+void
+ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
+{
+ uint16_t v, h;
+ for(v = 0; v < 8; v++)
+ for(h = 0; h < 8; h++) {
+ uint8_t ch1 = ((sprite[v] >> (7 - h)) & 0x1);
+ uint8_t ch2 = ((sprite[v + 8] >> (7 - h)) & 0x1);
+ uint8_t ch = ch1 + ch2 * 2;
+ if(ch || blending[4][color])
+ ppu_pixel(p,
+ layer,
+ x + (flipx ? 7 - h : h),
+ y + (flipy ? 7 - v : v),
+ blending[ch][color]);
+ }
+}
A src/devices/ppu.h => src/devices/ppu.h +46 -0
@@ 0,0 1,46 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#include <stdint.h>
+
+#define WIDTH_PIXELS 400
+#define STRIDE_PIXELS (52 * 8)
+#define STRIDE_WORDS (STRIDE_PIXELS / 32)
+#define HEIGHT 240
+
+/*
+ * framebuffer is the Playdate's raw framebuffer.
+ *
+ * pixels is the buffer for Varvara's two graphics layers and are grouped in
+ * three sets of 32 bits.
+ *
+ * pixels[0] carries the background color of the first 32 pixels,
+ * pixels[1] carries the foreground color, and
+ * pixels[2] carries the mask that switches between the two.
+ *
+ * Zero color bits are white, ones are black.
+ * Zero mask bits mean that the background color is shown, one is for the
+ * foreground color.
+ */
+
+typedef struct Ppu {
+ uint32_t pixels[HEIGHT * STRIDE_WORDS * 3];
+ uint32_t *framebuffer;
+ int color_is_white[4];
+} Ppu;
+
+int ppu_init(Ppu *p, uint32_t *framebuffer);
+void ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color);
+void ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
+void ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
+void ppu_clear(Ppu *p);
+void render_all(Ppu *p);
A src/devices/screen.c => src/devices/screen.c +184 -0
@@ 0,0 1,184 @@
+#include <stdlib.h>
+
+#include "../uxn.h"
+#include "screen.h"
+
+/*
+Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+UxnScreen uxn_screen;
+
+static Uint8 blending[5][16] = {
+ {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
+ {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
+ {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
+ {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
+ {1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};
+
+static void
+screen_write(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color)
+{
+ if(x < p->width && y < p->height) {
+ Uint32 i = x + y * p->width;
+ if(color != layer->pixels[i]) {
+ layer->pixels[i] = color;
+ layer->changed = 1;
+ }
+ }
+}
+
+static void
+screen_blit(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp)
+{
+ int v, h, opaque = blending[4][color];
+ for(v = 0; v < 8; v++) {
+ Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8;
+ for(h = 7; h >= 0; --h, c >>= 1) {
+ Uint8 ch = (c & 1) | ((c >> 7) & 2);
+ if(opaque || ch)
+ screen_write(p,
+ layer,
+ x + (flipx ? 7 - h : h),
+ y + (flipy ? 7 - v : v),
+ blending[ch][color]);
+ }
+ }
+}
+
+static void
+layer_clear(UxnScreen *p, Layer *layer)
+{
+ Uint32 i, size = p->width * p->height;
+ for(i = 0; i < size; i++)
+ layer->pixels[i] = 0x00;
+ layer->changed = 1;
+}
+
+void
+screen_palette(UxnScreen *p, Uint8 *addr)
+{
+ int i, shift;
+ for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
+ Uint8
+ r = (addr[0 + i / 2] >> shift) & 0x0f,
+ g = (addr[2 + i / 2] >> shift) & 0x0f,
+ b = (addr[4 + i / 2] >> shift) & 0x0f;
+ p->palette[i] = 0x0f000000 | r << 16 | g << 8 | b;
+ p->palette[i] |= p->palette[i] << 4;
+ }
+ p->fg.changed = p->bg.changed = 1;
+}
+
+void
+screen_resize(UxnScreen *p, Uint16 width, Uint16 height, Uint32 *pixels)
+{
+ Uint8
+ *bg = realloc(p->bg.pixels, width * height),
+ *fg = realloc(p->fg.pixels, width * height);
+ if(bg) p->bg.pixels = bg;
+ if(fg) p->fg.pixels = fg;
+ if(pixels) p->pixels = pixels;
+ if(bg && fg && pixels) {
+ p->width = width;
+ p->height = height;
+ screen_clear(p);
+ }
+}
+
+void
+screen_clear(UxnScreen *p)
+{
+ layer_clear(p, &p->bg);
+ layer_clear(p, &p->fg);
+}
+
+void
+screen_redraw(UxnScreen *p, Uint32 *pixels)
+{
+ Uint32 i, size = p->width * p->height, palette[16];
+ for(i = 0; i < 16; i++)
+ palette[i] = p->palette[(i >> 2) ? (i >> 2) : (i & 3)];
+ for(i = 0; i < size; i++)
+ pixels[i] = palette[p->fg.pixels[i] << 2 | p->bg.pixels[i]];
+ p->fg.changed = p->bg.changed = 0;
+}
+
+int
+clamp(int val, int min, int max)
+{
+ return (val >= min) ? (val <= max) ? val : max : min;
+}
+
+/* IO */
+
+Uint8
+screen_dei(Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0x2: return uxn_screen.width >> 8;
+ case 0x3: return uxn_screen.width;
+ case 0x4: return uxn_screen.height >> 8;
+ case 0x5: return uxn_screen.height;
+ default: return d[port];
+ }
+}
+
+void
+screen_deo(Uint8 *ram, Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0x3:
+ if(!FIXED_SIZE) {
+ Uint16 w;
+ PEKDEV(w, 0x2);
+ screen_resize(&uxn_screen, clamp(w, 1, 1024), uxn_screen.height, uxn_screen.pixels);
+ }
+ break;
+ case 0x5:
+ if(!FIXED_SIZE) {
+ Uint16 h;
+ PEKDEV(h, 0x4);
+ screen_resize(&uxn_screen, uxn_screen.width, clamp(h, 1, 1024), uxn_screen.pixels);
+ }
+ break;
+ case 0xe: {
+ Uint16 x, y;
+ Uint8 layer = d[0xe] & 0x40;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ screen_write(&uxn_screen, layer ? &uxn_screen.fg : &uxn_screen.bg, x, y, d[0xe] & 0x3);
+ if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
+ if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
+ break;
+ }
+ case 0xf: {
+ Uint16 x, y, dx, dy, addr;
+ Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
+ Layer *layer = (d[0xf] & 0x40) ? &uxn_screen.fg : &uxn_screen.bg;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ PEKDEV(addr, 0xc);
+ n = d[0x6] >> 4;
+ dx = (d[0x6] & 0x01) << 3;
+ dy = (d[0x6] & 0x02) << 2;
+ if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
+ return;
+ for(i = 0; i <= n; i++) {
+ screen_blit(&uxn_screen, layer, x + dy * i, y + dx * i, &ram[addr], d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20, twobpp);
+ addr += (d[0x6] & 0x04) << (1 + twobpp);
+ }
+ POKDEV(0xc, addr); /* auto addr+length */
+ POKDEV(0x8, x + dx); /* auto x+8 */
+ POKDEV(0xa, y + dy); /* auto y+8 */
+ break;
+ }
+ }
+}
A src/devices/screen.h => src/devices/screen.h +33 -0
@@ 0,0 1,33 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+#define FIXED_SIZE 1
+
+typedef struct Layer {
+ Uint8 *pixels, changed;
+} Layer;
+
+typedef struct UxnScreen {
+ Uint32 palette[4], *pixels;
+ Uint16 width, height;
+ Layer fg, bg;
+} UxnScreen;
+
+extern UxnScreen uxn_screen;
+
+void screen_palette(UxnScreen *p, Uint8 *addr);
+void screen_resize(UxnScreen *p, Uint16 width, Uint16 height, Uint32 *pixels);
+void screen_clear(UxnScreen *p);
+void screen_redraw(UxnScreen *p, Uint32 *pixels);
+
+Uint8 screen_dei(Uint8 *d, Uint8 port);
+void screen_deo(Uint8 *ram, Uint8 *d, Uint8 port);
+int clamp(int val, int min, int max);
A src/main-old.c => src/main-old.c +237 -0
@@ 0,0 1,237 @@
+
+#include "pd_api.h"
+#include "uxn.h"
+#include "devices/apu.h"
+#include "devices/ppu.h"
+
+static PlaydateAPI *pd = NULL;
+
+Uxn u;
+Ppu ppu;
+
+/*
+static unsigned char rom[] = {
+ 0xa0, 0x01, 0x39, 0x94, 0x80, 0x18, 0x17, 0x21, 0x94, 0x80, 0xf7, 0x0d, 0x22, 0x80, 0x22, 0x36,
+ 0x80, 0x01, 0x3f, 0xa0, 0x00, 0x08, 0x39, 0x80, 0x28, 0x37, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f,
+ 0xa0, 0x00, 0x08, 0x39, 0x80, 0x2a, 0x37, 0x80, 0x16, 0x80, 0x26, 0x17, 0xa0, 0x01, 0x47, 0x80,
+ 0x2c, 0x37, 0x80, 0x01, 0x80, 0x2f, 0x97, 0x17, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
+ 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x40, 0x48, 0x48, 0x00,
+ 0xe0, 0x18, 0x04, 0x04, 0x02, 0x12, 0x12, 0x40, 0x40, 0x44, 0x23, 0x20, 0x18, 0x07, 0x00, 0x02,
+ 0x02, 0x22, 0xc4, 0x04, 0x18, 0xe0, 0x00
+};
+
+*/
+
+unsigned char rom[] = {
+ 0xa0, 0x4c, 0xfd, 0x80, 0x08, 0x37, 0xa0, 0x4c, 0xf3, 0x80, 0x0a, 0x37, 0xa0, 0xdc, 0xf2, 0x80,
+ 0x0c, 0x37, 0xa0, 0x01, 0x4b, 0x80, 0x20, 0x37, 0x80, 0x22, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x00,
+ 0x31, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x02, 0x31, 0x80, 0x22, 0x36, 0xa0, 0x00, 0x20,
+ 0x39, 0xa0, 0x01, 0x5d, 0x35, 0x80, 0x24, 0x36, 0xa0, 0x00, 0x10, 0x39, 0xa0, 0x01, 0x80, 0x35,
+ 0x80, 0x36, 0x80, 0x26, 0x17, 0x80, 0x01, 0x80, 0x68, 0x0e, 0x00, 0x80, 0x00, 0x80, 0x62, 0x0e,
+ 0x80, 0x00, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x41, 0x0d, 0xef, 0xa0, 0x00, 0x00, 0x28,
+ 0x80, 0x39, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x04, 0x10, 0x26, 0x38, 0x38, 0xa0, 0xff, 0xff, 0x38,
+ 0x80, 0x00, 0x31, 0x80, 0x02, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x29, 0x0d, 0xef, 0xa0,
+ 0x00, 0x00, 0x28, 0x80, 0x21, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x05, 0x10, 0x26, 0x38, 0x38, 0xa0,
+ 0xff, 0xff, 0x38, 0x80, 0x02, 0x31, 0x80, 0x01, 0x80, 0x17, 0x0e, 0x00, 0x80, 0x04, 0x90, 0x80,
+ 0x00, 0x08, 0x04, 0x11, 0x80, 0xbc, 0x0c, 0x80, 0x05, 0x90, 0x80, 0x00, 0x08, 0x04, 0x11, 0x80,
+ 0xd4, 0x0c, 0xa0, 0x01, 0xc9, 0x80, 0x2c, 0x37, 0x80, 0x00, 0x30, 0x80, 0x28, 0x37, 0x80, 0x02,
+ 0x30, 0x80, 0x2a, 0x37, 0x80, 0x2f, 0x97, 0x17, 0x6c, 0x00, 0x1f, 0x3f, 0x38, 0x38, 0x38, 0x78,
+ 0x7f, 0x00, 0xfe, 0xfe, 0x7e, 0x77, 0x77, 0xe3, 0xc3, 0x00, 0x0f, 0x1f, 0x3b, 0x7b, 0x77, 0xe7,
+ 0xc7, 0x00, 0xfc, 0xfe, 0x8f, 0x87, 0x07, 0x0e, 0xfc, 0x7f, 0x00, 0x00, 0x0f, 0xff, 0x7f, 0x07,
+ 0x00, 0x03, 0x01, 0x00, 0xff, 0xf0, 0xf8, 0xff, 0x00, 0x87, 0x00, 0x00, 0xff, 0x7f, 0x7f, 0xff,
+ 0x00, 0xf0, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x80, 0x00,
+};
+
+
+/* Varvara */
+
+static const char *errors[] = {
+ "underflow",
+ "overflow",
+ "division by zero"};
+
+int
+uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr)
+{
+ pd->system->error("%s %s, by %02x at 0x%04x.\n", (instr & 0x40) ? "Return-stack" : "Working-stack", errors[err - 1], instr, addr);
+ return 0;
+}
+
+static int
+emu_error(char *msg, const char *err)
+{
+ pd->system->error("Error %s: %s\n", msg, err);
+ return 0;
+}
+
+static int
+emu_load(unsigned char *r)
+{
+ int i, len = sizeof(rom);
+ for(i = 0; i < len; i++){
+ u.ram[PAGE_PROGRAM + i] = rom[i];
+ }
+ return 1;
+}
+
+static void
+console_deo(Uint8 *d, Uint8 port)
+{
+ if(port == 0x8)
+ pd->system->logToConsole("%c", d[port]);
+}
+
+Uint8
+screen_dei(Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0x2: return 0x1;
+ case 0x3: return 0x90;
+ case 0x4: return 0x00;
+ case 0x5: return 0xf0;
+ default: return d[port];
+ }
+}
+
+void
+screen_deo(Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0xe: {
+ Uint16 x, y;
+ Uint8 layer = d[0xe] & 0x40;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ ppu_pixel(&ppu, !!layer, x, y, d[0xe] & 0x3);
+ if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
+ if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
+ pd->graphics->markUpdatedRows(y, y);
+ break;
+ }
+ case 0xf: {
+ Uint16 x, y, dx, dy, addr;
+ Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
+ Uint8 layer = d[0xe] & 0x40;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ PEKDEV(addr, 0xc);
+ n = d[0x6] >> 4;
+ dx = (d[0x6] & 0x01) << 3;
+ dy = (d[0x6] & 0x02) << 2;
+ if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
+ return;
+ if(d[0xf] & 0x80) {
+ ppu_2bpp(&ppu, !!layer, x, y, u.ram + addr, d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20);
+ } else {
+ ppu_1bpp(&ppu, !!layer, x, y, u.ram + addr, d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20);
+ }
+ POKDEV(0xc, addr); /* auto addr+length */
+ POKDEV(0x8, x + dx); /* auto x+8 */
+ POKDEV(0xa, y + dy); /* auto y+8 */
+ pd->graphics->markUpdatedRows(y, y + 7);
+ break;
+ }
+ }
+}
+
+static Uint8
+emu_dei(Uxn *u, Uint8 addr)
+{
+ Uint8 p = addr & 0x0f, d = addr & 0xf0;
+ switch(d) {
+ case 0x20: return screen_dei(&u->dev[d], p);
+ }
+ pd->system->logToConsole("%02x", addr);
+ return u->dev[addr];
+}
+
+static void
+emu_deo(Uxn *u, Uint8 addr, Uint8 v)
+{
+ Uint8 p = addr & 0x0f, d = addr & 0xf0;
+ Uint16 mask = 0x1 << (d >> 4);
+ u->dev[addr] = v;
+ switch(d) {
+ case 0x10: console_deo(&u->dev[d], p); break;
+ case 0x20: screen_deo(&u->dev[d], p); break;
+ }
+}
+
+static int
+emu_start (void){
+ if(!uxn_boot(&u, (Uint8 *)calloc(0x10300, sizeof(Uint8)), emu_dei, emu_deo))
+ return emu_error("Boot", "Failed");
+ if(!emu_load(rom))
+ return emu_error("Load", "Failed");
+ memset(&ppu, 0, sizeof(ppu));
+ if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
+ return 0;
+ if(!uxn_eval(&u, PAGE_PROGRAM))
+ return emu_error("Boot", "Failed to start rom.");
+ return 1;
+}
+
+/* Misc */
+
+static int update(void* userdata);
+const char* fontpath = "/System/Fonts/Asheville-Sans-14-Bold.pft";
+LCDFont* font = NULL;
+
+#ifdef _WINDLL
+__declspec(dllexport)
+#endif
+
+int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
+{
+ (void)arg; // arg is currently only used for event = kEventKeyPressed
+
+ if ( event == kEventInit )
+ {
+ pd = playdate;
+ pd->display->setRefreshRate(60);
+ if(!emu_start())
+ emu_error("Varvara", "Start failed.");
+ const char* err;
+ font = pd->graphics->loadFont(fontpath, &err);
+ if ( font == NULL )
+ pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontpath, err);
+ // Note: If you set an update callback in the kEventInit handler, the system assumes the game is pure C and doesn't run any Lua code in the game
+ pd->system->setUpdateCallback(update, pd);
+ }
+ return 0;
+}
+
+#define TEXT_WIDTH 86
+#define TEXT_HEIGHT 16
+
+int x = (400-TEXT_WIDTH)/2;
+int y = (240-TEXT_HEIGHT)/2;
+int dx = 1;
+int dy = 2;
+
+static int update(void* userdata)
+{
+ pd = userdata;
+
+/*
+ pd->graphics->clear(kColorWhite);
+ pd->graphics->setFont(font);
+ pd->graphics->drawText("Hello World!", strlen("Hello World!"), kASCIIEncoding, x, y);
+*/
+
+ uxn_eval(&u, GETVEC(&u.dev[0x20]));
+/*
+ x += dx;
+ y += dy;
+
+ if ( x < 0 || x > LCD_COLUMNS - TEXT_WIDTH )
+ dx = -dx;
+
+ if ( y < 0 || y > LCD_ROWS - TEXT_HEIGHT )
+ dy = -dy;
+ */
+ pd->system->drawFPS(0,0);
+
+ return 1;
+}
+
A src/main.c => src/main.c +245 -0
@@ 0,0 1,245 @@
+#include "pd_api.h"
+#include "uxn.h"
+#include "devices/apu.h"
+#include "devices/ppu.h"
+
+static PlaydateAPI *pd = NULL;
+
+Uxn u;
+Ppu ppu;
+
+/*
+static unsigned char rom[] = {
+ 0xa0, 0x01, 0x39, 0x94, 0x80, 0x18, 0x17, 0x21, 0x94, 0x80, 0xf7, 0x0d, 0x22, 0x80, 0x22, 0x36,
+ 0x80, 0x01, 0x3f, 0xa0, 0x00, 0x08, 0x39, 0x80, 0x28, 0x37, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f,
+ 0xa0, 0x00, 0x08, 0x39, 0x80, 0x2a, 0x37, 0x80, 0x16, 0x80, 0x26, 0x17, 0xa0, 0x01, 0x47, 0x80,
+ 0x2c, 0x37, 0x80, 0x01, 0x80, 0x2f, 0x97, 0x17, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
+ 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x40, 0x48, 0x48, 0x00,
+ 0xe0, 0x18, 0x04, 0x04, 0x02, 0x12, 0x12, 0x40, 0x40, 0x44, 0x23, 0x20, 0x18, 0x07, 0x00, 0x02,
+ 0x02, 0x22, 0xc4, 0x04, 0x18, 0xe0, 0x00
+};
+
+*/
+
+unsigned char rom[] = {
+ 0xa0, 0x4c, 0xfd, 0x80, 0x08, 0x37, 0xa0, 0x4c, 0xf3, 0x80, 0x0a, 0x37, 0xa0, 0xdc, 0xf2, 0x80,
+ 0x0c, 0x37, 0xa0, 0x01, 0x4b, 0x80, 0x20, 0x37, 0x80, 0x22, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x00,
+ 0x31, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x02, 0x31, 0x80, 0x22, 0x36, 0xa0, 0x00, 0x20,
+ 0x39, 0xa0, 0x01, 0x5d, 0x35, 0x80, 0x24, 0x36, 0xa0, 0x00, 0x10, 0x39, 0xa0, 0x01, 0x80, 0x35,
+ 0x80, 0x36, 0x80, 0x26, 0x17, 0x80, 0x01, 0x80, 0x68, 0x0e, 0x00, 0x80, 0x00, 0x80, 0x62, 0x0e,
+ 0x80, 0x00, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x41, 0x0d, 0xef, 0xa0, 0x00, 0x00, 0x28,
+ 0x80, 0x39, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x04, 0x10, 0x26, 0x38, 0x38, 0xa0, 0xff, 0xff, 0x38,
+ 0x80, 0x00, 0x31, 0x80, 0x02, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x29, 0x0d, 0xef, 0xa0,
+ 0x00, 0x00, 0x28, 0x80, 0x21, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x05, 0x10, 0x26, 0x38, 0x38, 0xa0,
+ 0xff, 0xff, 0x38, 0x80, 0x02, 0x31, 0x80, 0x01, 0x80, 0x17, 0x0e, 0x00, 0x80, 0x04, 0x90, 0x80,
+ 0x00, 0x08, 0x04, 0x11, 0x80, 0xbc, 0x0c, 0x80, 0x05, 0x90, 0x80, 0x00, 0x08, 0x04, 0x11, 0x80,
+ 0xd4, 0x0c, 0xa0, 0x01, 0xc9, 0x80, 0x2c, 0x37, 0x80, 0x00, 0x30, 0x80, 0x28, 0x37, 0x80, 0x02,
+ 0x30, 0x80, 0x2a, 0x37, 0x80, 0x2f, 0x97, 0x17, 0x6c, 0x00, 0x1f, 0x3f, 0x38, 0x38, 0x38, 0x78,
+ 0x7f, 0x00, 0xfe, 0xfe, 0x7e, 0x77, 0x77, 0xe3, 0xc3, 0x00, 0x0f, 0x1f, 0x3b, 0x7b, 0x77, 0xe7,
+ 0xc7, 0x00, 0xfc, 0xfe, 0x8f, 0x87, 0x07, 0x0e, 0xfc, 0x7f, 0x00, 0x00, 0x0f, 0xff, 0x7f, 0x07,
+ 0x00, 0x03, 0x01, 0x00, 0xff, 0xf0, 0xf8, 0xff, 0x00, 0x87, 0x00, 0x00, 0xff, 0x7f, 0x7f, 0xff,
+ 0x00, 0xf0, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x80, 0x00,
+};
+
+
+/* Varvara */
+
+static const char *errors[] = {
+ "underflow",
+ "overflow",
+ "division by zero"};
+
+int
+uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr)
+{
+ pd->system->error("%s %s, by %02x at 0x%04x.\n", (instr & 0x40) ? "Return-stack" : "Working-stack", errors[err - 1], instr, addr);
+ return 0;
+}
+
+static int
+emu_error(char *msg, const char *err)
+{
+ pd->system->error("Error %s: %s\n", msg, err);
+ return 0;
+}
+
+static int
+emu_load(unsigned char *r)
+{
+ int i, len = sizeof(rom);
+ for(i = 0; i < len; i++){
+ u.ram[PAGE_PROGRAM + i] = rom[i];
+ }
+ return 1;
+}
+
+static void
+console_deo(Uint8 *d, Uint8 port)
+{
+ if(port == 0x8)
+ pd->system->logToConsole("%c", d[port]);
+}
+
+Uint8
+screen_dei(Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0x2: return 0x1;
+ case 0x3: return 0x90;
+ case 0x4: return 0x00;
+ case 0x5: return 0xf0;
+ default: return d[port];
+ }
+}
+
+static uint8_t blending[5][16] = {
+ {0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
+ {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
+ {1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
+ {2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
+ {1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};
+
+static void
+screen_blit(Uint8 layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp)
+{
+ int v, h, opaque = blending[4][color];
+ for(v = 0; v < 8; v++) {
+ Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8;
+ for(h = 7; h >= 0; --h, c >>= 1) {
+ Uint8 ch = (c & 1) | ((c >> 7) & 2);
+ if(opaque || ch)
+ ppu_pixel(&ppu,
+ layer,
+ x + (flipx ? 7 - h : h),
+ y + (flipy ? 7 - v : v),
+ blending[ch][color]);
+ }
+ }
+}
+
+void
+screen_deo(Uint8 *d, Uint8 port)
+{
+ switch(port) {
+ case 0xe: {
+ Uint16 x, y;
+ Uint8 layer = d[0xe] & 0x40;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ ppu_pixel(&ppu, !!layer, x, y, d[0xe] & 0x3);
+ if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
+ if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
+ pd->graphics->markUpdatedRows(y, y);
+ break;
+ }
+ case 0xf: {
+ Uint16 x, y, dx, dy, addr;
+ Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
+ Uint8 layer = d[0xe] & 0x40;
+ PEKDEV(x, 0x8);
+ PEKDEV(y, 0xa);
+ PEKDEV(addr, 0xc);
+ n = d[0x6] >> 4;
+ dx = (d[0x6] & 0x01) << 3;
+ dy = (d[0x6] & 0x02) << 2;
+ if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
+ return;
+ if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
+ return;
+ for(i = 0; i <= n; i++) {
+ screen_blit(!!layer, x + dy * i, y + dx * i, &u.ram[addr], d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20, twobpp);
+ addr += (d[0x6] & 0x04) << (1 + twobpp);
+ }
+ POKDEV(0xc, addr); /* auto addr+length */
+ POKDEV(0x8, x + dx); /* auto x+8 */
+ POKDEV(0xa, y + dy); /* auto y+8 */
+ pd->graphics->markUpdatedRows(y, y + 7);
+ break;
+ }
+ }
+}
+
+static Uint8
+emu_dei(Uxn *u, Uint8 addr)
+{
+ Uint8 p = addr & 0x0f, d = addr & 0xf0;
+ switch(d) {
+ case 0x20: return screen_dei(&u->dev[d], p);
+ }
+ pd->system->logToConsole("%02x", addr);
+ return u->dev[addr];
+}
+
+static void
+emu_deo(Uxn *u, Uint8 addr, Uint8 v)
+{
+ Uint8 p = addr & 0x0f, d = addr & 0xf0;
+ Uint16 mask = 0x1 << (d >> 4);
+ u->dev[addr] = v;
+ switch(d) {
+ case 0x10: console_deo(&u->dev[d], p); break;
+ case 0x20: screen_deo(&u->dev[d], p); break;
+ }
+}
+
+static int
+emu_start (void){
+ if(!uxn_boot(&u, (Uint8 *)calloc(0x10300, sizeof(Uint8)), emu_dei, emu_deo))
+ return emu_error("Boot", "Failed");
+ if(!emu_load(rom))
+ return emu_error("Load", "Failed");
+ memset(&ppu, 0, sizeof(ppu));
+ if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
+ return 0;
+ if(!uxn_eval(&u, PAGE_PROGRAM))
+ return emu_error("Boot", "Failed to start rom.");
+ return 1;
+}
+
+/* Misc */
+
+static int update(void* userdata);
+const char* fontpath = "/System/Fonts/Asheville-Sans-14-Bold.pft";
+LCDFont* font = NULL;
+
+#ifdef _WINDLL
+__declspec(dllexport)
+#endif
+
+int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
+{
+ (void)arg; // arg is currently only used for event = kEventKeyPressed
+
+ if ( event == kEventInit )
+ {
+ pd = playdate;
+ pd->display->setRefreshRate(60);
+ if(!emu_start())
+ emu_error("Varvara", "Start failed.");
+ pd->graphics->clear(kColorBlack);
+ const char* err;
+ font = pd->graphics->loadFont(fontpath, &err);
+ if ( font == NULL )
+ pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontpath, err);
+ // Note: If you set an update callback in the kEventInit handler, the system assumes the game is pure C and doesn't run any Lua code in the game
+ pd->system->setUpdateCallback(update, pd);
+ }
+ return 0;
+}
+
+#define TEXT_WIDTH 86
+#define TEXT_HEIGHT 16
+
+int x = (400-TEXT_WIDTH)/2;
+int y = (240-TEXT_HEIGHT)/2;
+int dx = 1;
+int dy = 2;
+
+static int update(void* userdata)
+{
+ pd = userdata;
+ uxn_eval(&u, GETVEC(&u.dev[0x20]));
+ pd->system->drawFPS(0,0);
+ return 1;
+}
+
A src/uxn.c => src/uxn.c +112 -0
@@ 0,0 1,112 @@
+#include "uxn.h"
+
+/*
+Copyright (u) 2022 Devine Lu Linvega, Andrew Alderwick, Andrew Richards
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+/* clang-format off */
+
+#define PUSH8(s, x) { if(s->ptr == 0xff) { errcode = 2; goto err; } s->dat[s->ptr++] = (x); }
+#define PUSH16(s, x) { if((j = s->ptr) >= 0xfe) { errcode = 2; goto err; } k = (x); s->dat[j] = k >> 8; s->dat[j + 1] = k; s->ptr = j + 2; }
+#define PUSH(s, x) { if(bs) { PUSH16(s, (x)) } else { PUSH8(s, (x)) } }
+#define POP8(o) { if(!(j = *sp)) { errcode = 1; goto err; } o = (Uint16)src->dat[--j]; *sp = j; }
+#define POP16(o) { if((j = *sp) <= 1) { errcode = 1; goto err; } o = src->dat[j - 1]; o += src->dat[j - 2] << 8; *sp = j - 2; }
+#define POP(o) { if(bs) { POP16(o) } else { POP8(o) } }
+#define POKE(x, y) { if(bs) { u->ram[(x)] = (y) >> 8; u->ram[(x) + 1] = (y); } else { u->ram[(x)] = y; } }
+#define PEEK16(o, x) { o = (u->ram[(x)] << 8) + u->ram[(x) + 1]; }
+#define PEEK(o, x) { if(bs) { PEEK16(o, x) } else { o = u->ram[(x)]; } }
+#define DEVR(o, x) { o = u->dei(u, x); if (bs) o = (o << 8) + u->dei(u, ((x) + 1) & 0xFF); }
+#define DEVW(x, y) { if (bs) { u->deo(u, (x), (y) >> 8); u->deo(u, ((x) + 1) & 0xFF, (y)); } else { u->deo(u, x, (y)); } }
+#define JUMP(x) { if(bs) pc = (x); else pc += (Sint8)(x); }
+
+int
+uxn_eval(Uxn *u, Uint16 pc)
+{
+ unsigned int a, b, c, j, k, bs, instr, errcode;
+ Uint8 kptr, *sp;
+ Stack *src, *dst;
+ if(!pc || u->dev[0x0f]) return 0;
+ while((instr = u->ram[pc++])) {
+ /* Return Mode */
+ if(instr & 0x40) {
+ src = u->rst; dst = u->wst;
+ } else {
+ src = u->wst; dst = u->rst;
+ }
+ /* Keep Mode */
+ if(instr & 0x80) {
+ kptr = src->ptr;
+ sp = &kptr;
+ } else {
+ sp = &src->ptr;
+ }
+ /* Short Mode */
+ bs = instr & 0x20 ? 1 : 0;
+ switch(instr & 0x1f) {
+ /* Stack */
+ case 0x00: /* LIT */ PEEK(a, pc) PUSH(src, a) pc += 1 + bs; break;
+ case 0x01: /* INC */ POP(a) PUSH(src, a + 1) break;
+ case 0x02: /* POP */ POP(a) break;
+ case 0x03: /* NIP */ POP(a) POP(b) PUSH(src, a) break;
+ case 0x04: /* SWP */ POP(a) POP(b) PUSH(src, a) PUSH(src, b) break;
+ case 0x05: /* ROT */ POP(a) POP(b) POP(c) PUSH(src, b) PUSH(src, a) PUSH(src, c) break;
+ case 0x06: /* DUP */ POP(a) PUSH(src, a) PUSH(src, a) break;
+ case 0x07: /* OVR */ POP(a) POP(b) PUSH(src, b) PUSH(src, a) PUSH(src, b) break;
+ /* Logic */
+ case 0x08: /* EQU */ POP(a) POP(b) PUSH8(src, b == a) break;
+ case 0x09: /* NEQ */ POP(a) POP(b) PUSH8(src, b != a) break;
+ case 0x0a: /* GTH */ POP(a) POP(b) PUSH8(src, b > a) break;
+ case 0x0b: /* LTH */ POP(a) POP(b) PUSH8(src, b < a) break;
+ case 0x0c: /* JMP */ POP(a) JUMP(a) break;
+ case 0x0d: /* JCN */ POP(a) POP8(b) if(b) JUMP(a) break;
+ case 0x0e: /* JSR */ POP(a) PUSH16(dst, pc) JUMP(a) break;
+ case 0x0f: /* STH */ POP(a) PUSH(dst, a) break;
+ /* Memory */
+ case 0x10: /* LDZ */ POP8(a) PEEK(b, a) PUSH(src, b) break;
+ case 0x11: /* STZ */ POP8(a) POP(b) POKE(a, b) break;
+ case 0x12: /* LDR */ POP8(a) PEEK(b, pc + (Sint8)a) PUSH(src, b) break;
+ case 0x13: /* STR */ POP8(a) POP(b) c = pc + (Sint8)a; POKE(c, b) break;
+ case 0x14: /* LDA */ POP16(a) PEEK(b, a) PUSH(src, b) break;
+ case 0x15: /* STA */ POP16(a) POP(b) POKE(a, b) break;
+ case 0x16: /* DEI */ POP8(a) DEVR(b, a) PUSH(src, b) break;
+ case 0x17: /* DEO */ POP8(a) POP(b) DEVW(a, b) break;
+ /* Arithmetic */
+ case 0x18: /* ADD */ POP(a) POP(b) PUSH(src, b + a) break;
+ case 0x19: /* SUB */ POP(a) POP(b) PUSH(src, b - a) break;
+ case 0x1a: /* MUL */ POP(a) POP(b) PUSH(src, (Uint32)b * a) break;
+ case 0x1b: /* DIV */ POP(a) POP(b) if(a == 0) { errcode = 3; goto err; } PUSH(src, b / a) break;
+ case 0x1c: /* AND */ POP(a) POP(b) PUSH(src, b & a) break;
+ case 0x1d: /* ORA */ POP(a) POP(b) PUSH(src, b | a) break;
+ case 0x1e: /* EOR */ POP(a) POP(b) PUSH(src, b ^ a) break;
+ case 0x1f: /* SFT */ POP8(a) POP(b) PUSH(src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)) break;
+ }
+ }
+ return 1;
+err:
+ return uxn_halt(u, instr, errcode, pc - 1);
+}
+
+/* clang-format on */
+
+int
+uxn_boot(Uxn *u, Uint8 *ram, Uint8 (*dei)(struct Uxn *, Uint8), void (*deo)(struct Uxn *, Uint8, Uint8))
+{
+ Uint32 i;
+ char *cptr = (char *)u;
+ for(i = 0; i < sizeof(*u); i++)
+ cptr[i] = 0x00;
+ u->ram = ram;
+ u->wst = (Stack *)(ram + 0x10000);
+ u->rst = (Stack *)(ram + 0x10100);
+ u->dev = (Uint8 *)(ram + 0x10200);
+ u->dei = dei;
+ u->deo = deo;
+ return 1;
+}
A src/uxn.h => src/uxn.h +44 -0
@@ 0,0 1,44 @@
+/*
+Copyright (c) 2021 Devine Lu Linvega
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+typedef unsigned char Uint8;
+typedef signed char Sint8;
+typedef unsigned short Uint16;
+typedef signed short Sint16;
+typedef unsigned int Uint32;
+
+#define PAGE_PROGRAM 0x0100
+
+/* clang-format off */
+
+#define GETVEC(d) ((d)[0] << 8 | (d)[1])
+#define POKDEV(x, y) { d[(x)] = (y) >> 8; d[(x) + 1] = (y); }
+#define PEKDEV(o, x) { (o) = (d[(x)] << 8) + d[(x) + 1]; }
+
+/* clang-format on */
+
+typedef struct {
+ Uint8 dat[254], err, ptr;
+} Stack;
+
+typedef struct Uxn {
+ Uint8 *ram, *dev;
+ Stack *wst, *rst;
+ Uint8 (*dei)(struct Uxn *u, Uint8 addr);
+ void (*deo)(struct Uxn *u, Uint8 addr, Uint8 value);
+} Uxn;
+
+typedef Uint8 Dei(Uxn *u, Uint8 addr);
+typedef void Deo(Uxn *u, Uint8 addr, Uint8 value);
+
+int uxn_boot(Uxn *u, Uint8 *ram, Dei *dei, Deo *deo);
+int uxn_eval(Uxn *u, Uint16 pc);
+int uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr);