uxn

Varvara Ordinator, written in ANSI C(SDL2)
git clone https://git.eamoncaddigan.net/uxn.git
Log | Files | Refs | README | LICENSE

commit d163c432011f459ab39a823f513eef36724f3c32
parent 46658b7595ba1be87c0c395875ff00e7698bd04e
Author: neauoire <aliceffekt@gmail.com>
Date:   Wed, 26 May 2021 10:02:13 -0700

Improved uxncli and moved all demos into examples

Diffstat:
MREADME.md | 12+++++-------
Mbuild.sh | 31+++++++++++++------------------
Metc/asma-test.sh | 4++--
Metc/asma.lua | 2+-
Metc/asma.moon | 2+-
Metc/mkuxn-fast.lua | 2+-
Metc/mkuxn-fast.moon | 2+-
Mmkfile | 10+++++-----
Rprojects/demos/automata.usm -> projects/examples/demos/automata.usm | 0
Rprojects/demos/bifurcan.usm -> projects/examples/demos/bifurcan.usm | 0
Rprojects/demos/darena.usm -> projects/examples/demos/darena.usm | 0
Rprojects/demos/drum-rack.usm -> projects/examples/demos/drum-rack.usm | 0
Rprojects/demos/life.usm -> projects/examples/demos/life.usm | 0
Rprojects/demos/musictracker.usm -> projects/examples/demos/musictracker.usm | 0
Rprojects/demos/neralie.usm -> projects/examples/demos/neralie.usm | 0
Rprojects/demos/piano.usm -> projects/examples/demos/piano.usm | 0
Rprojects/demos/polycat.usm -> projects/examples/demos/polycat.usm | 0
Rprojects/demos/theme.usm -> projects/examples/demos/theme.usm | 0
Aprojects/examples/devices/audio.channels.usm | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mprojects/software/asma.usm | 2+-
Dsrc/assembler.c | 382-------------------------------------------------------------------------------
Dsrc/debugger.c | 122-------------------------------------------------------------------------------
Msrc/devices/ppu.c | 2+-
Msrc/devices/ppu.h | 2+-
Dsrc/emulator.c | 426-------------------------------------------------------------------------------
Asrc/uxnasm.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/uxncli.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/uxnemu.c | 425+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
28 files changed, 1128 insertions(+), 969 deletions(-)

diff --git a/README.md b/README.md @@ -1,6 +1,6 @@ # Uxn -An assembler and emulator for a [tiny stack-based computer](https://wiki.xxiivv.com/site/uxn.html), written in ANSI C. +An assembler and emulator for the [Uxn stack-machine](https://wiki.xxiivv.com/site/uxn.html), written in ANSI C. ## Build @@ -11,7 +11,6 @@ To build the Uxn emulator, you must have [SDL2](https://wiki.libsdl.org/). ```sh ./build.sh --debug # Add debug flags to compiler - --cli # Run rom without graphics ``` ### Plan 9 @@ -22,8 +21,7 @@ To build the Uxn emulator on [9front](http://9front.org/), via [npe](https://git mk ``` -If the build fails on 9front because of missing headers or functions, -try again after `rm -r /sys/include/npe`. +If the build fails on 9front because of missing headers or functions, try again after `rm -r /sys/include/npe`. ## Getting Started @@ -36,7 +34,7 @@ Begin by building the assembler and emulator by running the build script. The as The following command will create an Uxn-compatible rom from an [uxambly file](https://wiki.xxiivv.com/site/uxambly.html), point to a different usm file in `/projects` to assemble a different rom. ``` -bin/uxnasm projects/demos/life.usm bin/life.rom +bin/uxnasm projects/examples/demos/life.usm bin/life.rom ``` To start the rom, point the emulator to the newly created rom: @@ -45,11 +43,11 @@ To start the rom, point the emulator to the newly created rom: bin/uxnemu bin/life.rom ``` -You can find additional roms [here](https://sr.ht/~rabbits/uxn/sources). +You can also use the emulator without graphics by using `uxncli`. You can find additional roms [here](https://sr.ht/~rabbits/uxn/sources). ## Emulator Controls -- `ctrl+h` toggle debugger +- `ctrl+h` toggle inspector - `alt+h` toggle zoom ## Need a hand? diff --git a/build.sh b/build.sh @@ -9,14 +9,14 @@ clang-format -i src/devices/apu.h clang-format -i src/devices/apu.c clang-format -i src/devices/mpu.h clang-format -i src/devices/mpu.c -clang-format -i src/assembler.c -clang-format -i src/emulator.c -clang-format -i src/debugger.c +clang-format -i src/uxnasm.c +clang-format -i src/uxnemu.c +clang-format -i src/uxncli.c echo "Cleaning.." rm -f ./bin/uxnasm rm -f ./bin/uxnemu -rm -f ./bin/debugger +rm -f ./bin/uxncli rm -f ./bin/boot.rom echo "Building.." @@ -24,13 +24,13 @@ mkdir -p bin if [ "${1}" = '--debug' ]; then echo "[debug]" - cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/assembler.c -o bin/uxnasm - cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/devices/ppu.c src/devices/apu.c src/devices/mpu.c src/emulator.c -L/usr/local/lib -lSDL2 -o bin/uxnemu - cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/debugger.c -o bin/debugger + cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxnasm.c -o bin/uxnasm + cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/devices/ppu.c src/devices/apu.c src/devices/mpu.c src/uxnemu.c -L/usr/local/lib -lSDL2 -o bin/uxnemu + cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/uxncli.c -o bin/uxncli else - cc src/assembler.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/uxnasm - cc src/uxn-fast.c src/debugger.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/debugger - cc src/uxn-fast.c src/devices/ppu.c src/devices/apu.c src/devices/mpu.c src/emulator.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/uxnemu + cc src/uxnasm.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/uxnasm + cc src/uxn-fast.c src/uxncli.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/uxncli + cc src/uxn-fast.c src/devices/ppu.c src/devices/apu.c src/devices/mpu.c src/uxnemu.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/uxnemu fi echo "Installing.." @@ -38,19 +38,14 @@ if [ -d "$HOME/bin" ] && [ -e ./bin/uxnemu ] && [ -e ./bin/uxnasm ] then cp ./bin/uxnemu $HOME/bin cp ./bin/uxnasm $HOME/bin + cp ./bin/uxncli $HOME/bin echo "Installed in $HOME/bin" fi echo "Assembling.." -./bin/uxnasm projects/demos/piano.usm bin/boot.rom +./bin/uxnasm projects/examples/demos/piano.usm bin/piano.rom echo "Running.." -if [ "${2}" = '--cli' ]; -then - echo "[cli]" - ./bin/debugger bin/boot.rom -else - ./bin/uxnemu bin/boot.rom -fi +./bin/uxnemu bin/piano.rom echo "Done." diff --git a/etc/asma-test.sh b/etc/asma-test.sh @@ -22,7 +22,7 @@ EOD expect_failure() { cat > 'in.usm' - if ../bin/debugger asma.rom > asma.log 2>/dev/null || ! grep -qF "${1}" asma.log; then + if ../bin/uxncli asma.rom > asma.log 2>/dev/null || ! grep -qF "${1}" asma.log; then echo "error: asma didn't report error ${1} in faulty code" tail asma.log exit 1 @@ -44,7 +44,7 @@ find ../projects -type f -name '*.usm' -not -name 'blank.usm' | sort | while rea xxd "uxnasm-${BN}.rom" > "uxnasm-${BN}.hex" cp "${F}" 'in.usm' - if ! ../bin/debugger asma.rom > asma.log; then + if ! ../bin/uxncli asma.rom > asma.log; then echo "error: asma failed to assemble ${F}, while uxnasm succeeded" tail asma.log exit 1 diff --git a/etc/asma.lua b/etc/asma.lua @@ -23,7 +23,7 @@ local trees = { local opcodes_in_order = { } do local wanted = false - for l in assert(io.lines('src/assembler.c')) do + for l in assert(io.lines('src/uxnasm.c')) do if l == 'char ops[][4] = {' then wanted = true elseif wanted then diff --git a/etc/asma.moon b/etc/asma.moon @@ -34,7 +34,7 @@ opcodes_in_order = {} do -- opcodes wanted = false - for l in assert io.lines 'src/assembler.c' + for l in assert io.lines 'src/uxnasm.c' if l == 'char ops[][4] = {' wanted = true elseif wanted diff --git a/etc/mkuxn-fast.lua b/etc/mkuxn-fast.lua @@ -219,7 +219,7 @@ for l in assert(io.lines('src/uxn.c')) do end i = 0 wanted = false -for l in assert(io.lines('src/assembler.c')) do +for l in assert(io.lines('src/uxnasm.c')) do if l == 'char ops[][4] = {' then wanted = true elseif l == '};' then diff --git a/etc/mkuxn-fast.moon b/etc/mkuxn-fast.moon @@ -156,7 +156,7 @@ for l in assert io.lines 'src/uxn.c' i = 0 wanted = false -for l in assert io.lines 'src/assembler.c' +for l in assert io.lines 'src/uxnasm.c' if l == 'char ops[][4] = {' wanted = true elseif l == '};' diff --git a/mkfile b/mkfile @@ -1,6 +1,6 @@ </$objtype/mkfile -TARG=bin/debugger bin/uxnasm bin/uxnemu +TARG=bin/uxncli bin/uxnasm bin/uxnemu USM=`{walk -f projects/ | grep '\.usm$' | grep -v blank.usm} ROM=${USM:%.usm=%.rom} CFLAGS=$CFLAGS -I/sys/include/npe @@ -29,16 +29,16 @@ bin: %.rom:Q: %.usm bin/uxnasm bin/uxnasm $stem.usm $target >/dev/null -bin/debugger: debugger.$O uxn.$O +bin/uxncli: uxncli.$O uxn.$O $LD $LDFLAGS -o $target $prereq -bin/uxnasm: assembler.$O +bin/uxnasm: uxnasm.$O $LD $LDFLAGS -o $target $prereq -bin/uxnemu: emulator.$O apu.$O mpu.$O ppu.$O uxn.$O +bin/uxnemu: uxnemu.$O apu.$O mpu.$O ppu.$O uxn.$O $LD $LDFLAGS -o $target $prereq -(assembler|debugger|emulator|uxn)\.$O:R: src/\1.c +(uxnasm|uxncli|uxnemu|uxn)\.$O:R: src/\1.c $CC $CFLAGS -Isrc -o $target src/$stem1.c (apu|mpu|ppu)\.$O:R: src/devices/\1.c diff --git a/projects/demos/automata.usm b/projects/examples/demos/automata.usm diff --git a/projects/demos/bifurcan.usm b/projects/examples/demos/bifurcan.usm diff --git a/projects/demos/darena.usm b/projects/examples/demos/darena.usm diff --git a/projects/demos/drum-rack.usm b/projects/examples/demos/drum-rack.usm diff --git a/projects/demos/life.usm b/projects/examples/demos/life.usm diff --git a/projects/demos/musictracker.usm b/projects/examples/demos/musictracker.usm diff --git a/projects/demos/neralie.usm b/projects/examples/demos/neralie.usm diff --git a/projects/demos/piano.usm b/projects/examples/demos/piano.usm diff --git a/projects/demos/polycat.usm b/projects/examples/demos/polycat.usm diff --git a/projects/demos/theme.usm b/projects/examples/demos/theme.usm diff --git a/projects/examples/devices/audio.channels.usm b/projects/examples/devices/audio.channels.usm @@ -0,0 +1,143 @@ +( dev/audio ) + +%4** { #20 SFT2 } +%8** { #30 SFT2 } +%MOD { DUP2 DIV MUL SUB } + +( devices ) + +|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ] +|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &color $1 ] +|30 @Audio0 [ &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1 ] +|40 @Audio1 [ &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1 ] +|50 @Audio2 [ &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1 ] +|60 @Audio3 [ &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1 ] + +( variables ) + +|0000 + +@timer $1 +@counter $1 + +|0100 ( -> ) + + ( theme ) + #00ff .System/r DEO2 + #0f0f .System/g DEO2 + #0ff0 .System/b DEO2 + + ( vectors ) + ;on-frame .Screen/vector DEO2 + + ( setup synth ) + #1202 .Audio0/adsr DEO2 + ;saw .Audio0/addr DEO2 + #0100 .Audio0/length DEO2 + #88 .Audio0/volume DEO + + #0101 .Audio1/adsr DEO2 + ;tri .Audio1/addr DEO2 + #0100 .Audio1/length DEO2 + #88 .Audio1/volume DEO + + #0112 .Audio2/adsr DEO2 + ;sin .Audio2/addr DEO2 + #0100 .Audio2/length DEO2 + #88 .Audio2/volume DEO + + #0022 .Audio3/adsr DEO2 + ;piano .Audio3/addr DEO2 + #0100 .Audio3/length DEO2 + #88 .Audio3/volume DEO + +BRK + +@on-frame ( -> ) + + ( incr ) .timer LDZ #01 ADD .timer STZ + ( skip ) .timer LDZ #10 EQU #01 JCN [ BRK ] + + ( get note ) + .counter LDZ #18 MOD #30 ADD + .Audio0/pitch .counter LDZ #04 MOD #10 MUL ADD DEO + + .counter LDZ #01 ADD .counter STZ + #00 .timer STZ + +BRK + +@saw + 0003 0609 0c0f 1215 181b 1e21 2427 2a2d + 3033 3639 3b3e 4143 4649 4b4e 5052 5557 + 595b 5e60 6264 6667 696b 6c6e 7071 7274 + 7576 7778 797a 7b7b 7c7d 7d7e 7e7e 7e7e + 7f7e 7e7e 7e7e 7d7d 7c7b 7b7a 7978 7776 + 7574 7271 706e 6c6b 6967 6664 6260 5e5b + 5957 5552 504e 4b49 4643 413e 3b39 3633 + 302d 2a27 2421 1e1b 1815 120f 0c09 0603 + 00fd faf7 f4f1 eeeb e8e5 e2df dcd9 d6d3 + d0cd cac7 c5c2 bfbd bab7 b5b2 b0ae aba9 + a7a5 a2a0 9e9c 9a99 9795 9492 908f 8e8c + 8b8a 8988 8786 8585 8483 8382 8282 8282 + 8182 8282 8282 8383 8485 8586 8788 898a + 8b8c 8e8f 9092 9495 9799 9a9c 9ea0 a2a5 + a7a9 abae b0b2 b5b7 babd bfc2 c5c7 cacd + d0d3 d6d9 dcdf e2e5 e8eb eef1 f4f7 fafd +@tri + 8082 8486 888a 8c8e 9092 9496 989a 9c9e + a0a2 a4a6 a8aa acae b0b2 b4b6 b8ba bcbe + c0c2 c4c6 c8ca ccce d0d2 d4d6 d8da dcde + e0e2 e4e6 e8ea ecee f0f2 f4f6 f8fa fcfe + fffd fbf9 f7f5 f3f1 efed ebe9 e7e5 e3e1 + dfdd dbd9 d7d5 d3d1 cfcd cbc9 c7c5 c3c1 + bfbd bbb9 b7b5 b3b1 afad aba9 a7a5 a3a1 + 9f9d 9b99 9795 9391 8f8d 8b89 8785 8381 + 7f7d 7b79 7775 7371 6f6d 6b69 6765 6361 + 5f5d 5b59 5755 5351 4f4d 4b49 4745 4341 + 3f3d 3b39 3735 3331 2f2d 2b29 2725 2321 + 1f1d 1b19 1715 1311 0f0d 0b09 0705 0301 + 0103 0507 090b 0d0f 1113 1517 191b 1d1f + 2123 2527 292b 2d2f 3133 3537 393b 3d3f + 4143 4547 494b 4d4f 5153 5557 595b 5d5f + 6163 6567 696b 6d6f 7173 7577 797b 7d7f +@sin + 8083 8689 8c8f 9295 989b 9ea1 a4a7 aaad + b0b3 b6b9 bbbe c1c3 c6c9 cbce d0d2 d5d7 + d9db dee0 e2e4 e6e7 e9eb ecee f0f1 f2f4 + f5f6 f7f8 f9fa fbfb fcfd fdfe fefe fefe + fffe fefe fefe fdfd fcfb fbfa f9f8 f7f6 + f5f4 f2f1 f0ee eceb e9e7 e6e4 e2e0 dedb + d9d7 d5d2 d0ce cbc9 c6c3 c1be bbb9 b6b3 + b0ad aaa7 a4a1 9e9b 9895 928f 8c89 8683 + 807d 7a77 7471 6e6b 6865 625f 5c59 5653 + 504d 4a47 4542 3f3d 3a37 3532 302e 2b29 + 2725 2220 1e1c 1a19 1715 1412 100f 0e0c + 0b0a 0908 0706 0505 0403 0302 0202 0202 + 0102 0202 0202 0303 0405 0506 0708 090a + 0b0c 0e0f 1012 1415 1719 1a1c 1e20 2225 + 2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d + 5053 5659 5c5f 6265 686b 6e71 7477 7a7d +@piano + 8182 8588 8d91 959b a1a6 aaad b2b5 b8bd + c1c7 cbd0 d5d9 dde1 e5e5 e4e4 e1dc d7d1 + cbc5 bfb8 b2ac a6a2 9c97 928d 8884 807c + 7977 7574 7372 7272 7273 7372 706d 6964 + 605b 5650 4d49 4643 4342 4244 4548 4a4d + 5052 5556 5758 5554 5150 4c4a 4744 423f + 3d3c 3a38 3835 3431 3030 2f31 3336 393e + 4449 4e54 5a60 666b 7175 7b82 8990 989e + a6ab b1b6 babd bebf bfbe bbb9 b6b3 b0ae + aaa8 a6a3 a19e 9c9a 9997 9696 9798 9b9e + a1a4 a6a9 a9ac adad adae aeaf b0b0 b1b1 + b3b3 b4b4 b4b3 b3b1 b0ad abab a9a9 a8a8 + a7a5 a19d 9891 8b84 7e77 726e 6b6b 6b6c + 6f71 7477 7776 7370 6c65 5e56 4e48 423f + 3d3c 3b3a 3a39 3838 3839 393a 3c3e 4146 + 4a50 575b 6064 686a 6e70 7274 7677 7a7d + +@melody [ + 54 52 54 4f 4b 4f 48 ff + 54 52 54 4f 4b 4f 48 ff + 54 56 57 56 57 54 56 54 + 56 52 54 52 54 50 54 ff ] diff --git a/projects/software/asma.usm b/projects/software/asma.usm @@ -9,7 +9,7 @@ Asma - an in-Uxn assembler This assembler aims to be binary compatible with the output from - src/assembler.c, but unlike that assembler this one can be run inside Uxn + src/uxnasm.c, but unlike that assembler this one can be run inside Uxn itself! Asma is designed to be able to be copy-pasted inside another project, so diff --git a/src/assembler.c b/src/assembler.c @@ -1,382 +0,0 @@ -#include <stdio.h> - -/* -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. -*/ - -#define TRIM 0x0100 - -typedef unsigned char Uint8; -typedef signed char Sint8; -typedef unsigned short Uint16; - -typedef struct { - char name[64], items[64][64]; - Uint8 len, refs; -} Macro; - -typedef struct { - char name[64]; - Uint8 refs; - Uint16 addr; -} Label; - -typedef struct { - Uint8 data[256 * 256], mlen; - Uint16 ptr, length, llen; - Label labels[512]; - Macro macros[256]; -} Program; - -Program p; - -/* clang-format off */ - -char ops[][4] = { - "BRK", "LIT", "NOP", "POP", "DUP", "SWP", "OVR", "ROT", - "EQU", "NEQ", "GTH", "LTH", "JMP", "JCN", "JSR", "STH", - "LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO", - "ADD", "SUB", "MUL", "DIV", "AND", "ORA", "EOR", "SFT" -}; - -int scin(char *s, char c) { int i = 0; while(s[i]) if(s[i++] == c) return i - 1; return -1; } /* string char index */ -int scmp(char *a, char *b, int len) { int i = 0; while(a[i] == b[i] && i < len) if(!a[i++]) return 1; return 0; } /* string compare */ -int sihx(char *s) { int i = 0; char c; while((c = s[i++])) if(!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f')) return 0; return 1; } /* string is hexadecimal */ -int ssin(char *s, char *ss) { int a = 0, b = 0; while(s[a]) { if(s[a] == ss[b]) { if(!ss[b + 1]) return a - b; b++; } else b = 0; a++; } return -1; } /* string substring index */ -int shex(char *s) { int n = 0, i = 0; char c; while((c = s[i++])) if(c >= '0' && c <= '9') n = n * 16 + (c - '0'); else if(c >= 'a' && c <= 'f') n = n * 16 + 10 + (c - 'a'); return n; } /* string to num */ -int slen(char *s) { int i = 0; while(s[i] && s[++i]) ; return i; } /* string length */ -char *scpy(char *src, char *dst, int len) { int i = 0; while((dst[i] = src[i]) && i < len - 2) i++; dst[i + 1] = '\0'; return dst; } /* string copy */ -char *scat(char *dst, const char *src) { char *ptr = dst + slen(dst); while(*src) *ptr++ = *src++; *ptr = '\0'; return dst; } /* string cat */ - -#pragma mark - Helpers - -/* clang-format on */ - -#pragma mark - I/O - -void -pushbyte(Uint8 b, int lit) -{ - if(lit) pushbyte(0x01, 0); - p.data[p.ptr++] = b; - p.length = p.ptr; -} - -void -pushshort(Uint16 s, int lit) -{ - if(lit) pushbyte(0x21, 0); - pushbyte((s >> 8) & 0xff, 0); - pushbyte(s & 0xff, 0); -} - -void -pushword(char *s) -{ - int i = 0; - char c; - while((c = s[i++])) pushbyte(c, 0); -} - -Macro * -findmacro(char *name) -{ - int i; - for(i = 0; i < p.mlen; ++i) - if(scmp(p.macros[i].name, name, 64)) - return &p.macros[i]; - return NULL; -} - -Label * -findlabel(char *name) -{ - int i; - for(i = 0; i < p.llen; ++i) - if(scmp(p.labels[i].name, name, 64)) - return &p.labels[i]; - return NULL; -} - -Uint8 -findopcode(char *s) -{ - int i; - for(i = 0; i < 0x20; ++i) { - int m = 0; - char *o = ops[i]; - if(o[0] != s[0] || o[1] != s[1] || o[2] != s[2]) - continue; - while(s[3 + m]) { - if(s[3 + m] == '2') - i |= (1 << 5); /* mode: short */ - else if(s[3 + m] == 'r') - i |= (1 << 6); /* mode: return */ - else if(s[3 + m] == 'k') - i |= (1 << 7); /* mode: keep */ - else - return 0; /* failed to match */ - m++; - } - return i; - } - return 0; -} - -char * -sublabel(char *src, char *scope, char *name) -{ - return scat(scat(scpy(scope, src, 64), "/"), name); -} - -#pragma mark - Parser - -int -error(char *name, char *id) -{ - printf("Error: %s[%s]\n", name, id); - return 0; -} - -int -makemacro(char *name, FILE *f) -{ - Macro *m; - char word[64]; - if(findmacro(name)) - return error("Macro duplicate", name); - if(sihx(name) && slen(name) % 2 == 0) - return error("Macro name is hex number", name); - if(findopcode(name)) - return error("Macro name is invalid", name); - m = &p.macros[p.mlen++]; - scpy(name, m->name, 64); - while(fscanf(f, "%s", word)) { - if(word[0] == '{') continue; - if(word[0] == '}') break; - if(m->len > 64) - return error("Macro too large", name); - if(slen(word) >= 64) - return error("Word too long", name); - scpy(word, m->items[m->len++], 64); - } - printf("New macro: %s, %d items\n", m->name, m->len); - return 1; -} - -int -makelabel(char *name, Uint16 addr) -{ - Label *l; - if(findlabel(name)) - return error("Label duplicate", name); - if(sihx(name) && slen(name) % 2 == 0) - return error("Label name is hex number", name); - if(findopcode(name)) - return error("Label name is invalid", name); - l = &p.labels[p.llen++]; - l->addr = addr; - l->refs = 0; - scpy(name, l->name, 64); - printf("New label: %s, at 0x%04x\n", l->name, l->addr); - return 1; -} - -int -skipblock(char *w, int *cap, char a, char b) -{ - if(w[0] == b) { - *cap = 0; - return 1; - } - if(w[0] == a) *cap = 1; - if(*cap) return 1; - return 0; -} - -int -walktoken(char *w) -{ - Macro *m; - if(findopcode(w) || scmp(w, "BRK", 4)) - return 1; - switch(w[0]) { - case '[': return 0; - case ']': return 0; - case '\'': return 1; - case '.': return 2; /* zero-page: LIT addr-lb */ - case ',': return 2; /* relative: LIT addr-rel */ - case ':': return 2; /* absolute: addr-hb addr-lb */ - case ';': return 3; /* absolute: LIT addr-hb addr-lb */ - case '$': return shex(w + 1); - case '#': return slen(w + 1) == 4 ? 3 : 2; - case '"': return slen(w + 1); - } - if((m = findmacro(w))) { - int i, res = 0; - for(i = 0; i < m->len; ++i) - res += walktoken(m->items[i]); - return res; - } - return error("Unknown label in first pass", w); -} - -int -parsetoken(char *w) -{ - Label *l; - Macro *m; - if(w[0] == '.' && (l = findlabel(w + 1))) { /* zero-page */ - if(l->addr > 0xff) - return error("Address is not in zero page", w); - pushbyte(l->addr, 1); - return ++l->refs; - } else if(w[0] == ',' && (l = findlabel(w + 1))) { /* relative */ - int off = l->addr - p.ptr - 3; - if(off < -126 || off > 126) - return error("Address is too far", w); - pushbyte((Sint8)off, 1); - return ++l->refs; - } else if(w[0] == ':' && (l = findlabel(w + 1))) { /* raw */ - pushshort(l->addr, 0); - return ++l->refs; - } else if(w[0] == ';' && (l = findlabel(w + 1))) { /* absolute */ - pushshort(l->addr, 1); - return ++l->refs; - } else if(findopcode(w) || scmp(w, "BRK", 4)) { /* opcode */ - pushbyte(findopcode(w), 0); - return 1; - } else if(w[0] == '"') { /* string */ - pushword(w + 1); - return 1; - } else if(w[0] == '\'') { /* char */ - pushbyte((Uint8)w[1], 0); - return 1; - } else if(w[0] == '#') { /* immediate */ - if(slen(w + 1) == 1) - pushbyte((Uint8)w[1], 1); - if(sihx(w + 1) && slen(w + 1) == 2) - pushbyte(shex(w + 1), 1); - else if(sihx(w + 1) && slen(w + 1) == 4) - pushshort(shex(w + 1), 1); - else - return 0; - return 1; - } else if(sihx(w)) { /* raw */ - if(slen(w) == 2) - pushbyte(shex(w), 0); - else if(slen(w) == 4) - pushshort(shex(w), 0); - else - return error("Hex value length is invalid", w); - return 1; - } else if((m = findmacro(w))) { - int i; - for(i = 0; i < m->len; ++i) - if(!parsetoken(m->items[i])) - return 0; - return ++m->refs; - } - return 0; -} - -int -pass1(FILE *f) -{ - int ccmnt = 0; - Uint16 addr = 0; - char w[64], scope[64], subw[64]; - printf("Pass 1\n"); - while(fscanf(f, "%s", w) == 1) { - if(skipblock(w, &ccmnt, '(', ')')) continue; - if(w[0] == '|') { - addr = shex(w + 1); - } else if(w[0] == '%') { - if(!makemacro(w + 1, f)) - return error("Pass1 failed", w); - } else if(w[0] == '@') { - if(!makelabel(w + 1, addr)) - return error("Pass1 failed", w); - scpy(w + 1, scope, 64); - } else if(w[0] == '&') { - if(!makelabel(sublabel(subw, scope, w + 1), addr)) - return error("Pass1 failed", w); - } else if(sihx(w)) - addr += slen(w) / 2; - else - addr += walktoken(w); - } - rewind(f); - return 1; -} - -int -pass2(FILE *f) -{ - int ccmnt = 0, cmacr = 0; - char w[64], scope[64], subw[64]; - printf("Pass 2\n"); - while(fscanf(f, "%s", w) == 1) { - if(w[0] == '%') continue; - if(w[0] == '&') continue; - if(w[0] == '[') continue; - if(w[0] == ']') continue; - if(skipblock(w, &ccmnt, '(', ')')) continue; - if(skipblock(w, &cmacr, '{', '}')) continue; - if(w[0] == '|') { - if(p.length && shex(w + 1) < p.ptr) - return error("Memory Overwrite", w); - p.ptr = shex(w + 1); - continue; - } else if(w[0] == '$') { - p.ptr += shex(w + 1); - continue; - } else if(w[0] == '@') { - scpy(w + 1, scope, 64); - continue; - } - if(w[1] == '&') - scpy(sublabel(subw, scope, w + 2), w + 1, 64); - if(!parsetoken(w)) - return error("Unknown label in second pass", w); - } - return 1; -} - -void -cleanup(char *filename) -{ - int i; - printf("Assembled %s(%d bytes), %d labels, %d macros.\n\n", filename, (p.length - TRIM), p.llen, p.mlen); - for(i = 0; i < p.llen; ++i) - if(p.labels[i].name[0] >= 'A' && p.labels[i].name[0] <= 'Z') - continue; /* Ignore capitalized labels(devices) */ - else if(!p.labels[i].refs) - printf("--- Unused label: %s\n", p.labels[i].name); - for(i = 0; i < p.mlen; ++i) - if(!p.macros[i].refs) - printf("--- Unused macro: %s\n", p.macros[i].name); -} - -int -main(int argc, char *argv[]) -{ - FILE *f; - if(argc < 3) - return !error("Input", "Missing"); - if(!(f = fopen(argv[1], "r"))) - return !error("Open", "Failed"); - if(!pass1(f) || !pass2(f)) - return !error("Assembly", "Failed"); - fwrite(p.data + TRIM, p.length - TRIM, 1, fopen(argv[2], "wb")); - fclose(f); - cleanup(argv[2]); - return 0; -} diff --git a/src/debugger.c b/src/debugger.c @@ -1,122 +0,0 @@ -#include <stdio.h> -#include "uxn.h" - -/* -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. -*/ - -#pragma mark - Core - -int -error(char *msg, const char *err) -{ - printf("Error %s: %s\n", msg, err); - return 0; -} - -void -printstack(Stack *s) -{ - Uint8 x, y; - for(y = 0; y < 0x08; ++y) { - for(x = 0; x < 0x08; ++x) { - Uint8 p = y * 0x08 + x; - printf(p == s->ptr ? "[%02x]" : " %02x ", s->dat[p]); - } - printf("\n"); - } -} - -#pragma mark - Devices - -void -console_talk(Device *d, Uint8 b0, Uint8 w) -{ - if(!w) return; - switch(b0) { - case 0x8: printf("%c", d->dat[0x8]); break; - case 0x9: printf("0x%02x", d->dat[0x9]); break; - case 0xb: printf("0x%04x", mempeek16(d->dat, 0xa)); break; - case 0xd: printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; - } - fflush(stdout); -} - -void -file_talk(Device *d, Uint8 b0, Uint8 w) -{ - Uint8 read = b0 == 0xd; - if(w && (read || b0 == 0xf)) { - char *name = (char *)&d->mem[mempeek16(d->dat, 0x8)]; - Uint16 result = 0, length = mempeek16(d->dat, 0xa); - Uint16 offset = mempeek16(d->dat, 0x4); - Uint16 addr = mempeek16(d->dat, b0 - 1); - FILE *f = fopen(name, read ? "r" : (offset ? "a" : "w")); - if(f) { - if(fseek(f, offset, SEEK_SET) != -1 && (result = read ? fread(&d->mem[addr], 1, length, f) : fwrite(&d->mem[addr], 1, length, f))) - printf("%s %d bytes, at %04x from %s\n", read ? "Loaded" : "Saved", result, addr, name); - fclose(f); - } - mempoke16(d->dat, 0x2, result); - } -} - -void -nil_talk(Device *d, Uint8 b0, Uint8 w) -{ - (void)d; - (void)b0; - (void)w; -} - -#pragma mark - Generics - -int -start(Uxn *u) -{ - printf("RESET --------\n"); - if(!evaluxn(u, PAGE_PROGRAM)) - return error("Reset", "Failed"); - printstack(&u->wst); - return 1; -} - -int -main(int argc, char **argv) -{ - Uxn u; - - if(argc < 2) - return error("Input", "Missing"); - if(!bootuxn(&u)) - return error("Boot", "Failed"); - if(!loaduxn(&u, argv[1])) - return error("Load", "Failed"); - - portuxn(&u, 0x00, "empty", nil_talk); - portuxn(&u, 0x01, "console", console_talk); - portuxn(&u, 0x02, "empty", nil_talk); - portuxn(&u, 0x03, "empty", nil_talk); - portuxn(&u, 0x04, "empty", nil_talk); - portuxn(&u, 0x05, "empty", nil_talk); - portuxn(&u, 0x06, "empty", nil_talk); - portuxn(&u, 0x07, "empty", nil_talk); - portuxn(&u, 0x08, "empty", nil_talk); - portuxn(&u, 0x09, "empty", nil_talk); - portuxn(&u, 0x0a, "file", file_talk); - portuxn(&u, 0x0b, "empty", nil_talk); - portuxn(&u, 0x0c, "empty", nil_talk); - portuxn(&u, 0x0d, "empty", nil_talk); - portuxn(&u, 0x0e, "empty", nil_talk); - portuxn(&u, 0x0f, "empty", nil_talk); - start(&u); - - return 0; -} diff --git a/src/devices/ppu.c b/src/devices/ppu.c @@ -107,7 +107,7 @@ putchr(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uin /* output */ void -drawdebugger(Ppu *p, Uint8 *stack, Uint8 ptr) +inspect(Ppu *p, Uint8 *stack, Uint8 ptr) { Uint8 i, x, y, b; for(i = 0; i < 0x20; ++i) { /* memory */ diff --git a/src/devices/ppu.h b/src/devices/ppu.h @@ -31,4 +31,4 @@ void putcolors(Ppu *p, Uint8 *addr); void putpixel(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color); void puticn(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy); void putchr(Ppu *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy); -void drawdebugger(Ppu *p, Uint8 *stack, Uint8 ptr); +void inspect(Ppu *p, Uint8 *stack, Uint8 ptr); diff --git a/src/emulator.c b/src/emulator.c @@ -1,426 +0,0 @@ -#include <SDL2/SDL.h> -#include <stdio.h> -#include <time.h> -#include "uxn.h" -#include "devices/ppu.h" -#include "devices/apu.h" -#include "devices/mpu.h" - -/* -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. -*/ - -static SDL_AudioDeviceID audio_id; -static SDL_Window *gWindow; -static SDL_Renderer *gRenderer; -static SDL_Texture *fgTexture, *bgTexture; -static SDL_Rect gRect; -static Ppu ppu; -static Apu apu[POLYPHONY]; -static Mpu mpu; -static Device *devscreen, *devmouse, *devctrl, *devmidi, *devaudio0; - -#define PAD 16 - -Uint8 zoom = 0, debug = 0, reqdraw = 0, bench = 0; - -int -clamp(int val, int min, int max) -{ - return (val >= min) ? (val <= max) ? val : max : min; -} - -int -error(char *msg, const char *err) -{ - printf("Error %s: %s\n", msg, err); - return 0; -} - -static void -audio_callback(void *u, Uint8 *stream, int len) -{ - int i; - Sint16 *samples = (Sint16 *)stream; - SDL_memset(stream, 0, len); - for(i = 0; i < POLYPHONY; ++i) - apu_render(&apu[i], samples, samples + len / 2); - (void)u; -} - -void -redraw(Uxn *u) -{ - if(debug) - drawdebugger(&ppu, u->wst.dat, u->wst.ptr); - SDL_UpdateTexture(bgTexture, &gRect, ppu.bg.pixels, ppu.width * sizeof(Uint32)); - SDL_UpdateTexture(fgTexture, &gRect, ppu.fg.pixels, ppu.width * sizeof(Uint32)); - SDL_RenderClear(gRenderer); - SDL_RenderCopy(gRenderer, bgTexture, NULL, NULL); - SDL_RenderCopy(gRenderer, fgTexture, NULL, NULL); - SDL_RenderPresent(gRenderer); - reqdraw = 0; -} - -void -toggledebug(Uxn *u) -{ - debug = !debug; - redraw(u); -} - -void -togglezoom(Uxn *u) -{ - zoom = zoom == 3 ? 1 : zoom + 1; - SDL_SetWindowSize(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom); - redraw(u); -} - -void -quit(void) -{ - free(ppu.fg.pixels); - free(ppu.bg.pixels); - SDL_UnlockAudioDevice(audio_id); - SDL_DestroyTexture(bgTexture); - bgTexture = NULL; - SDL_DestroyTexture(fgTexture); - fgTexture = NULL; - SDL_DestroyRenderer(gRenderer); - gRenderer = NULL; - SDL_DestroyWindow(gWindow); - gWindow = NULL; - SDL_Quit(); - exit(0); -} - -int -init(void) -{ - SDL_AudioSpec as; - if(!initppu(&ppu, 48, 32)) - return error("PPU", "Init failure"); - gRect.x = PAD; - gRect.y = PAD; - gRect.w = ppu.width; - gRect.h = ppu.height; - if(!initmpu(&mpu, 1)) - return error("MPU", "Init failure"); - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) - return error("Init", SDL_GetError()); - gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom, SDL_WINDOW_SHOWN); - if(gWindow == NULL) - return error("Window", SDL_GetError()); - gRenderer = SDL_CreateRenderer(gWindow, -1, 0); - if(gRenderer == NULL) - return error("Renderer", SDL_GetError()); - SDL_RenderSetLogicalSize(gRenderer, ppu.width + PAD * 2, ppu.height + PAD * 2); - bgTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2); - if(bgTexture == NULL || SDL_SetTextureBlendMode(bgTexture, SDL_BLENDMODE_NONE)) - return error("Texture", SDL_GetError()); - fgTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2); - if(fgTexture == NULL || SDL_SetTextureBlendMode(fgTexture, SDL_BLENDMODE_BLEND)) - return error("Texture", SDL_GetError()); - SDL_UpdateTexture(bgTexture, NULL, ppu.bg.pixels, 4); - SDL_UpdateTexture(fgTexture, NULL, ppu.fg.pixels, 4); - SDL_StartTextInput(); - SDL_ShowCursor(SDL_DISABLE); - SDL_zero(as); - as.freq = SAMPLE_FREQUENCY; - as.format = AUDIO_S16; - as.channels = 2; - as.callback = audio_callback; - as.samples = 512; - as.userdata = NULL; - audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); - if(!audio_id) - return error("Audio", SDL_GetError()); - SDL_PauseAudioDevice(audio_id, 0); - return 1; -} - -void -domouse(SDL_Event *event) -{ - Uint8 flag = 0x00; - Uint16 x = clamp(event->motion.x - PAD, 0, ppu.hor * 8 - 1); - Uint16 y = clamp(event->motion.y - PAD, 0, ppu.ver * 8 - 1); - mempoke16(devmouse->dat, 0x2, x); - mempoke16(devmouse->dat, 0x4, y); - devmouse->dat[7] = 0x00; - switch(event->button.button) { - case SDL_BUTTON_LEFT: flag = 0x01; break; - case SDL_BUTTON_RIGHT: flag = 0x10; break; - } - switch(event->type) { - case SDL_MOUSEBUTTONDOWN: - devmouse->dat[6] |= flag; - if(flag == 0x10 && (devmouse->dat[6] & 0x01)) - devmouse->dat[7] = 0x01; - if(flag == 0x01 && (devmouse->dat[6] & 0x10)) - devmouse->dat[7] = 0x10; - break; - case SDL_MOUSEBUTTONUP: - devmouse->dat[6] &= (~flag); - break; - } -} - -void -doctrl(Uxn *u, SDL_Event *event, int z) -{ - Uint8 flag = 0x00; - if(z && event->key.keysym.sym == SDLK_h) { - if(SDL_GetModState() & KMOD_LCTRL) - toggledebug(u); - if(SDL_GetModState() & KMOD_LALT) - togglezoom(u); - } - switch(event->key.keysym.sym) { - case SDLK_LCTRL: flag = 0x01; break; - case SDLK_LALT: flag = 0x02; break; - case SDLK_LSHIFT: flag = 0x04; break; - case SDLK_ESCAPE: flag = 0x08; break; - case SDLK_UP: flag = 0x10; break; - case SDLK_DOWN: flag = 0x20; break; - case SDLK_LEFT: flag = 0x40; break; - case SDLK_RIGHT: flag = 0x80; break; - } - if(flag && z) - devctrl->dat[2] |= flag; - else if(flag) - devctrl->dat[2] &= (~flag); - if(z && event->key.keysym.sym < 20) - devctrl->dat[3] = event->key.keysym.sym; -} - -#pragma mark - Devices - -void -system_talk(Device *d, Uint8 b0, Uint8 w) -{ - if(!w) { - d->dat[0x2] = d->u->wst.ptr; - d->dat[0x3] = d->u->rst.ptr; - } else { - putcolors(&ppu, &d->dat[0x8]); - reqdraw = 1; - } - (void)b0; -} - -void -console_talk(Device *d, Uint8 b0, Uint8 w) -{ - if(!w) return; - switch(b0) { - case 0x8: printf("%c", d->dat[0x8]); break; - case 0x9: printf("0x%02x", d->dat[0x9]); break; - case 0xb: printf("0x%04x", mempeek16(d->dat, 0xa)); break; - case 0xd: printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; - } - fflush(stdout); -} - -void -screen_talk(Device *d, Uint8 b0, Uint8 w) -{ - if(w && b0 == 0xe) { - Uint16 x = mempeek16(d->dat, 0x8); - Uint16 y = mempeek16(d->dat, 0xa); - Uint8 *addr = &d->mem[mempeek16(d->dat, 0xc)]; - Layer *layer = d->dat[0xe] >> 4 & 0x1 ? &ppu.fg : &ppu.bg; - Uint8 mode = d->dat[0xe] >> 5; - if(!mode) - putpixel(&ppu, layer, x, y, d->dat[0xe] & 0x3); - else if(mode-- & 0x1) - puticn(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); - else - putchr(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); - - reqdraw = 1; - } -} - -void -file_talk(Device *d, Uint8 b0, Uint8 w) -{ - Uint8 read = b0 == 0xd; - if(w && (read || b0 == 0xf)) { - char *name = (char *)&d->mem[mempeek16(d->dat, 0x8)]; - Uint16 result = 0, length = mempeek16(d->dat, 0xa); - Uint16 offset = mempeek16(d->dat, 0x4); - Uint16 addr = mempeek16(d->dat, b0 - 1); - FILE *f = fopen(name, read ? "r" : (offset ? "a" : "w")); - if(f) { - printf("%s %04x %s %s: ", read ? "Loading" : "Saving", addr, read ? "from" : "to", name); - if(fseek(f, offset, SEEK_SET) != -1) - result = read ? fread(&d->mem[addr], 1, length, f) : fwrite(&d->mem[addr], 1, length, f); - printf("%04x bytes\n", result); - fclose(f); - } - mempoke16(d->dat, 0x2, result); - } -} - -static void -audio_talk(Device *d, Uint8 b0, Uint8 w) -{ - Apu *c = &apu[d - devaudio0]; - if(!w) { - if(b0 == 0x2) - mempoke16(d->dat, 0x2, c->i); - else if(b0 == 0x4) - d->dat[0x4] = apu_get_vu(c); - } else if(b0 == 0xf) { - SDL_LockAudioDevice(audio_id); - c->len = mempeek16(d->dat, 0xa); - c->addr = &d->mem[mempeek16(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, mempeek16(d->dat, 0x8), d->dat[0xf] & 0x7f); - SDL_UnlockAudioDevice(audio_id); - } -} - -void -datetime_talk(Device *d, Uint8 b0, Uint8 w) -{ - time_t seconds = time(NULL); - struct tm *t = localtime(&seconds); - t->tm_year += 1900; - mempoke16(d->dat, 0x0, t->tm_year); - d->dat[0x2] = t->tm_mon; - d->dat[0x3] = t->tm_mday; - d->dat[0x4] = t->tm_hour; - d->dat[0x5] = t->tm_min; - d->dat[0x6] = t->tm_sec; - d->dat[0x7] = t->tm_wday; - mempoke16(d->dat, 0x08, t->tm_yday); - d->dat[0xa] = t->tm_isdst; - (void)b0; - (void)w; -} - -void -midi_talk(Device *d, Uint8 b0, Uint8 w) -{ - (void)d; - (void)b0; - (void)w; -} - -void -nil_talk(Device *d, Uint8 b0, Uint8 w) -{ - (void)d; - (void)b0; - (void)w; -} - -#pragma mark - Generics - -int -start(Uxn *u) -{ - evaluxn(u, 0x0100); - redraw(u); - while(1) { - int i; - SDL_Event event; - double elapsed, start = 0; - if(!bench) - start = SDL_GetPerformanceCounter(); - while(SDL_PollEvent(&event) != 0) { - switch(event.type) { - case SDL_QUIT: - quit(); - break; - case SDL_TEXTINPUT: - case SDL_KEYDOWN: - case SDL_KEYUP: - if(event.text.text[0] >= ' ' && event.text.text[0] <= '~') - devctrl->dat[3] = event.text.text[0]; - doctrl(u, &event, event.type == SDL_KEYDOWN); - evaluxn(u, mempeek16(devctrl->dat, 0)); - devctrl->dat[3] = 0; - break; - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEMOTION: - domouse(&event); - evaluxn(u, mempeek16(devmouse->dat, 0)); - break; - case SDL_WINDOWEVENT: - if(event.window.event == SDL_WINDOWEVENT_EXPOSED) - redraw(u); - break; - } - } - listenmpu(&mpu); - for(i = 0; i < mpu.queue; ++i) { - devmidi->dat[2] = mpu.events[i].message; - devmidi->dat[3] = mpu.events[i].message >> 8; - devmidi->dat[4] = mpu.events[i].message >> 16; - evaluxn(u, mempeek16(devmidi->dat, 0)); - } - evaluxn(u, mempeek16(devscreen->dat, 0)); - if(reqdraw) - redraw(u); - if(!bench) { - elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0f; - SDL_Delay(clamp(16.666f - elapsed, 0, 1000)); - } - } - return 1; -} - -int -main(int argc, char **argv) -{ - Uxn u; - zoom = 2; - - if(argc < 2) - return error("Input", "Missing"); - if(!bootuxn(&u)) - return error("Boot", "Failed"); - if(!loaduxn(&u, argv[1])) - return error("Load", "Failed"); - if(!init()) - return error("Init", "Failed"); - - portuxn(&u, 0x0, "system", system_talk); - portuxn(&u, 0x1, "console", console_talk); - devscreen = portuxn(&u, 0x2, "screen", screen_talk); - devaudio0 = portuxn(&u, 0x3, "audio0", audio_talk); - portuxn(&u, 0x4, "audio1", audio_talk); - portuxn(&u, 0x5, "audio2", audio_talk); - portuxn(&u, 0x6, "audio3", audio_talk); - devmidi = portuxn(&u, 0x7, "midi", midi_talk); - devctrl = portuxn(&u, 0x8, "controller", nil_talk); - devmouse = portuxn(&u, 0x9, "mouse", nil_talk); - portuxn(&u, 0xa, "file", file_talk); - portuxn(&u, 0xb, "datetime", datetime_talk); - portuxn(&u, 0xc, "---", nil_talk); - portuxn(&u, 0xd, "---", nil_talk); - portuxn(&u, 0xe, "---", nil_talk); - portuxn(&u, 0xf, "---", nil_talk); - - /* Write screen size to dev/screen */ - mempoke16(devscreen->dat, 2, ppu.hor * 8); - mempoke16(devscreen->dat, 4, ppu.ver * 8); - - start(&u); - quit(); - return 0; -} diff --git a/src/uxnasm.c b/src/uxnasm.c @@ -0,0 +1,382 @@ +#include <stdio.h> + +/* +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. +*/ + +#define TRIM 0x0100 + +typedef unsigned char Uint8; +typedef signed char Sint8; +typedef unsigned short Uint16; + +typedef struct { + char name[64], items[64][64]; + Uint8 len, refs; +} Macro; + +typedef struct { + char name[64]; + Uint8 refs; + Uint16 addr; +} Label; + +typedef struct { + Uint8 data[256 * 256], mlen; + Uint16 ptr, length, llen; + Label labels[512]; + Macro macros[256]; +} Program; + +Program p; + +/* clang-format off */ + +char ops[][4] = { + "BRK", "LIT", "NOP", "POP", "DUP", "SWP", "OVR", "ROT", + "EQU", "NEQ", "GTH", "LTH", "JMP", "JCN", "JSR", "STH", + "LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO", + "ADD", "SUB", "MUL", "DIV", "AND", "ORA", "EOR", "SFT" +}; + +int scin(char *s, char c) { int i = 0; while(s[i]) if(s[i++] == c) return i - 1; return -1; } /* string char index */ +int scmp(char *a, char *b, int len) { int i = 0; while(a[i] == b[i] && i < len) if(!a[i++]) return 1; return 0; } /* string compare */ +int sihx(char *s) { int i = 0; char c; while((c = s[i++])) if(!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f')) return 0; return 1; } /* string is hexadecimal */ +int ssin(char *s, char *ss) { int a = 0, b = 0; while(s[a]) { if(s[a] == ss[b]) { if(!ss[b + 1]) return a - b; b++; } else b = 0; a++; } return -1; } /* string substring index */ +int shex(char *s) { int n = 0, i = 0; char c; while((c = s[i++])) if(c >= '0' && c <= '9') n = n * 16 + (c - '0'); else if(c >= 'a' && c <= 'f') n = n * 16 + 10 + (c - 'a'); return n; } /* string to num */ +int slen(char *s) { int i = 0; while(s[i] && s[++i]) ; return i; } /* string length */ +char *scpy(char *src, char *dst, int len) { int i = 0; while((dst[i] = src[i]) && i < len - 2) i++; dst[i + 1] = '\0'; return dst; } /* string copy */ +char *scat(char *dst, const char *src) { char *ptr = dst + slen(dst); while(*src) *ptr++ = *src++; *ptr = '\0'; return dst; } /* string cat */ + +#pragma mark - Helpers + +/* clang-format on */ + +#pragma mark - I/O + +void +pushbyte(Uint8 b, int lit) +{ + if(lit) pushbyte(0x01, 0); + p.data[p.ptr++] = b; + p.length = p.ptr; +} + +void +pushshort(Uint16 s, int lit) +{ + if(lit) pushbyte(0x21, 0); + pushbyte((s >> 8) & 0xff, 0); + pushbyte(s & 0xff, 0); +} + +void +pushword(char *s) +{ + int i = 0; + char c; + while((c = s[i++])) pushbyte(c, 0); +} + +Macro * +findmacro(char *name) +{ + int i; + for(i = 0; i < p.mlen; ++i) + if(scmp(p.macros[i].name, name, 64)) + return &p.macros[i]; + return NULL; +} + +Label * +findlabel(char *name) +{ + int i; + for(i = 0; i < p.llen; ++i) + if(scmp(p.labels[i].name, name, 64)) + return &p.labels[i]; + return NULL; +} + +Uint8 +findopcode(char *s) +{ + int i; + for(i = 0; i < 0x20; ++i) { + int m = 0; + char *o = ops[i]; + if(o[0] != s[0] || o[1] != s[1] || o[2] != s[2]) + continue; + while(s[3 + m]) { + if(s[3 + m] == '2') + i |= (1 << 5); /* mode: short */ + else if(s[3 + m] == 'r') + i |= (1 << 6); /* mode: return */ + else if(s[3 + m] == 'k') + i |= (1 << 7); /* mode: keep */ + else + return 0; /* failed to match */ + m++; + } + return i; + } + return 0; +} + +char * +sublabel(char *src, char *scope, char *name) +{ + return scat(scat(scpy(scope, src, 64), "/"), name); +} + +#pragma mark - Parser + +int +error(char *name, char *id) +{ + printf("Error: %s[%s]\n", name, id); + return 0; +} + +int +makemacro(char *name, FILE *f) +{ + Macro *m; + char word[64]; + if(findmacro(name)) + return error("Macro duplicate", name); + if(sihx(name) && slen(name) % 2 == 0) + return error("Macro name is hex number", name); + if(findopcode(name)) + return error("Macro name is invalid", name); + m = &p.macros[p.mlen++]; + scpy(name, m->name, 64); + while(fscanf(f, "%s", word)) { + if(word[0] == '{') continue; + if(word[0] == '}') break; + if(m->len > 64) + return error("Macro too large", name); + if(slen(word) >= 64) + return error("Word too long", name); + scpy(word, m->items[m->len++], 64); + } + printf("New macro: %s, %d items\n", m->name, m->len); + return 1; +} + +int +makelabel(char *name, Uint16 addr) +{ + Label *l; + if(findlabel(name)) + return error("Label duplicate", name); + if(sihx(name) && slen(name) % 2 == 0) + return error("Label name is hex number", name); + if(findopcode(name)) + return error("Label name is invalid", name); + l = &p.labels[p.llen++]; + l->addr = addr; + l->refs = 0; + scpy(name, l->name, 64); + printf("New label: %s, at 0x%04x\n", l->name, l->addr); + return 1; +} + +int +skipblock(char *w, int *cap, char a, char b) +{ + if(w[0] == b) { + *cap = 0; + return 1; + } + if(w[0] == a) *cap = 1; + if(*cap) return 1; + return 0; +} + +int +walktoken(char *w) +{ + Macro *m; + if(findopcode(w) || scmp(w, "BRK", 4)) + return 1; + switch(w[0]) { + case '[': return 0; + case ']': return 0; + case '\'': return 1; + case '.': return 2; /* zero-page: LIT addr-lb */ + case ',': return 2; /* relative: LIT addr-rel */ + case ':': return 2; /* absolute: addr-hb addr-lb */ + case ';': return 3; /* absolute: LIT addr-hb addr-lb */ + case '$': return shex(w + 1); + case '#': return slen(w + 1) == 4 ? 3 : 2; + case '"': return slen(w + 1); + } + if((m = findmacro(w))) { + int i, res = 0; + for(i = 0; i < m->len; ++i) + res += walktoken(m->items[i]); + return res; + } + return error("Unknown label in first pass", w); +} + +int +parsetoken(char *w) +{ + Label *l; + Macro *m; + if(w[0] == '.' && (l = findlabel(w + 1))) { /* zero-page */ + if(l->addr > 0xff) + return error("Address is not in zero page", w); + pushbyte(l->addr, 1); + return ++l->refs; + } else if(w[0] == ',' && (l = findlabel(w + 1))) { /* relative */ + int off = l->addr - p.ptr - 3; + if(off < -126 || off > 126) + return error("Address is too far", w); + pushbyte((Sint8)off, 1); + return ++l->refs; + } else if(w[0] == ':' && (l = findlabel(w + 1))) { /* raw */ + pushshort(l->addr, 0); + return ++l->refs; + } else if(w[0] == ';' && (l = findlabel(w + 1))) { /* absolute */ + pushshort(l->addr, 1); + return ++l->refs; + } else if(findopcode(w) || scmp(w, "BRK", 4)) { /* opcode */ + pushbyte(findopcode(w), 0); + return 1; + } else if(w[0] == '"') { /* string */ + pushword(w + 1); + return 1; + } else if(w[0] == '\'') { /* char */ + pushbyte((Uint8)w[1], 0); + return 1; + } else if(w[0] == '#') { /* immediate */ + if(slen(w + 1) == 1) + pushbyte((Uint8)w[1], 1); + if(sihx(w + 1) && slen(w + 1) == 2) + pushbyte(shex(w + 1), 1); + else if(sihx(w + 1) && slen(w + 1) == 4) + pushshort(shex(w + 1), 1); + else + return 0; + return 1; + } else if(sihx(w)) { /* raw */ + if(slen(w) == 2) + pushbyte(shex(w), 0); + else if(slen(w) == 4) + pushshort(shex(w), 0); + else + return error("Hex value length is invalid", w); + return 1; + } else if((m = findmacro(w))) { + int i; + for(i = 0; i < m->len; ++i) + if(!parsetoken(m->items[i])) + return 0; + return ++m->refs; + } + return 0; +} + +int +pass1(FILE *f) +{ + int ccmnt = 0; + Uint16 addr = 0; + char w[64], scope[64], subw[64]; + printf("Pass 1\n"); + while(fscanf(f, "%s", w) == 1) { + if(skipblock(w, &ccmnt, '(', ')')) continue; + if(w[0] == '|') + addr = shex(w + 1); + else if(w[0] == '%') { + if(!makemacro(w + 1, f)) + return error("Pass1 failed", w); + } else if(w[0] == '@') { + if(!makelabel(w + 1, addr)) + return error("Pass1 failed", w); + scpy(w + 1, scope, 64); + } else if(w[0] == '&') { + if(!makelabel(sublabel(subw, scope, w + 1), addr)) + return error("Pass1 failed", w); + } else if(sihx(w)) + addr += slen(w) / 2; + else + addr += walktoken(w); + } + rewind(f); + return 1; +} + +int +pass2(FILE *f) +{ + int ccmnt = 0, cmacr = 0; + char w[64], scope[64], subw[64]; + printf("Pass 2\n"); + while(fscanf(f, "%s", w) == 1) { + if(w[0] == '%') continue; + if(w[0] == '&') continue; + if(w[0] == '[') continue; + if(w[0] == ']') continue; + if(skipblock(w, &ccmnt, '(', ')')) continue; + if(skipblock(w, &cmacr, '{', '}')) continue; + if(w[0] == '|') { + if(p.length && shex(w + 1) < p.ptr) + return error("Memory Overwrite", w); + p.ptr = shex(w + 1); + continue; + } else if(w[0] == '$') { + p.ptr += shex(w + 1); + continue; + } else if(w[0] == '@') { + scpy(w + 1, scope, 64); + continue; + } + if(w[1] == '&') + scpy(sublabel(subw, scope, w + 2), w + 1, 64); + if(!parsetoken(w)) + return error("Unknown label in second pass", w); + } + return 1; +} + +void +cleanup(char *filename) +{ + int i; + printf("Assembled %s(%d bytes), %d labels, %d macros.\n\n", filename, (p.length - TRIM), p.llen, p.mlen); + for(i = 0; i < p.llen; ++i) + if(p.labels[i].name[0] >= 'A' && p.labels[i].name[0] <= 'Z') + continue; /* Ignore capitalized labels(devices) */ + else if(!p.labels[i].refs) + printf("--- Unused label: %s\n", p.labels[i].name); + for(i = 0; i < p.mlen; ++i) + if(!p.macros[i].refs) + printf("--- Unused macro: %s\n", p.macros[i].name); +} + +int +main(int argc, char *argv[]) +{ + FILE *f; + if(argc < 3) + return !error("Input", "Missing"); + if(!(f = fopen(argv[1], "r"))) + return !error("Open", "Failed"); + if(!pass1(f) || !pass2(f)) + return !error("Assembly", "Failed"); + fwrite(p.data + TRIM, p.length - TRIM, 1, fopen(argv[2], "wb")); + fclose(f); + cleanup(argv[2]); + return 0; +} diff --git a/src/uxncli.c b/src/uxncli.c @@ -0,0 +1,146 @@ +#include <stdio.h> +#include <time.h> +#include "uxn.h" + +/* +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. +*/ + +#pragma mark - Core + +int +error(char *msg, const char *err) +{ + printf("Error %s: %s\n", msg, err); + return 0; +} + +void +printstack(Stack *s) +{ + Uint8 x, y; + printf("\n\n"); + for(y = 0; y < 0x08; ++y) { + for(x = 0; x < 0x08; ++x) { + Uint8 p = y * 0x08 + x; + printf(p == s->ptr ? "[%02x]" : " %02x ", s->dat[p]); + } + printf("\n"); + } +} + +#pragma mark - Devices + +void +console_talk(Device *d, Uint8 b0, Uint8 w) +{ + if(!w) return; + switch(b0) { + case 0x8: printf("%c", d->dat[0x8]); break; + case 0x9: printf("0x%02x", d->dat[0x9]); break; + case 0xb: printf("0x%04x", mempeek16(d->dat, 0xa)); break; + case 0xd: printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; + } + fflush(stdout); +} + +void +file_talk(Device *d, Uint8 b0, Uint8 w) +{ + Uint8 read = b0 == 0xd; + if(w && (read || b0 == 0xf)) { + char *name = (char *)&d->mem[mempeek16(d->dat, 0x8)]; + Uint16 result = 0, length = mempeek16(d->dat, 0xa); + Uint16 offset = mempeek16(d->dat, 0x4); + Uint16 addr = mempeek16(d->dat, b0 - 1); + FILE *f = fopen(name, read ? "r" : (offset ? "a" : "w")); + if(f) { + printf("%s %04x %s %s: ", read ? "Loading" : "Saving", addr, read ? "from" : "to", name); + if(fseek(f, offset, SEEK_SET) != -1) + result = read ? fread(&d->mem[addr], 1, length, f) : fwrite(&d->mem[addr], 1, length, f); + printf("%04x bytes\n", result); + fclose(f); + } + mempoke16(d->dat, 0x2, result); + } +} + +void +datetime_talk(Device *d, Uint8 b0, Uint8 w) +{ + time_t seconds = time(NULL); + struct tm *t = localtime(&seconds); + t->tm_year += 1900; + mempoke16(d->dat, 0x0, t->tm_year); + d->dat[0x2] = t->tm_mon; + d->dat[0x3] = t->tm_mday; + d->dat[0x4] = t->tm_hour; + d->dat[0x5] = t->tm_min; + d->dat[0x6] = t->tm_sec; + d->dat[0x7] = t->tm_wday; + mempoke16(d->dat, 0x08, t->tm_yday); + d->dat[0xa] = t->tm_isdst; + (void)b0; + (void)w; +} + +void +nil_talk(Device *d, Uint8 b0, Uint8 w) +{ + (void)d; + (void)b0; + (void)w; +} + +#pragma mark - Generics + +int +start(Uxn *u) +{ + if(!evaluxn(u, PAGE_PROGRAM)) + return error("Reset", "Failed"); + return 1; +} + +int +main(int argc, char **argv) +{ + Uxn u; + + if(argc < 2) + return error("Input", "Missing"); + if(!bootuxn(&u)) + return error("Boot", "Failed"); + if(!loaduxn(&u, argv[1])) + return error("Load", "Failed"); + + portuxn(&u, 0x0, "empty", nil_talk); + portuxn(&u, 0x1, "console", console_talk); + portuxn(&u, 0x2, "empty", nil_talk); + portuxn(&u, 0x3, "empty", nil_talk); + portuxn(&u, 0x4, "empty", nil_talk); + portuxn(&u, 0x5, "empty", nil_talk); + portuxn(&u, 0x6, "empty", nil_talk); + portuxn(&u, 0x7, "empty", nil_talk); + portuxn(&u, 0x8, "empty", nil_talk); + portuxn(&u, 0x9, "empty", nil_talk); + portuxn(&u, 0xa, "file", file_talk); + portuxn(&u, 0xb, "datetime", datetime_talk); + portuxn(&u, 0xc, "empty", nil_talk); + portuxn(&u, 0xd, "empty", nil_talk); + portuxn(&u, 0xe, "empty", nil_talk); + portuxn(&u, 0xf, "empty", nil_talk); + + start(&u); + + if(argc > 2) + printstack(&u.wst); + return 0; +} diff --git a/src/uxnemu.c b/src/uxnemu.c @@ -0,0 +1,425 @@ +#include <SDL2/SDL.h> +#include <stdio.h> +#include <time.h> +#include "uxn.h" +#include "devices/ppu.h" +#include "devices/apu.h" +#include "devices/mpu.h" + +/* +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. +*/ + +static SDL_AudioDeviceID audio_id; +static SDL_Window *gWindow; +static SDL_Renderer *gRenderer; +static SDL_Texture *fgTexture, *bgTexture; +static SDL_Rect gRect; +static Ppu ppu; +static Apu apu[POLYPHONY]; +static Mpu mpu; +static Device *devscreen, *devmouse, *devctrl, *devmidi, *devaudio0; + +#define PAD 16 + +Uint8 zoom = 0, debug = 0, reqdraw = 0, bench = 0; + +int +clamp(int val, int min, int max) +{ + return (val >= min) ? (val <= max) ? val : max : min; +} + +int +error(char *msg, const char *err) +{ + printf("Error %s: %s\n", msg, err); + return 0; +} + +static void +audio_callback(void *u, Uint8 *stream, int len) +{ + int i; + Sint16 *samples = (Sint16 *)stream; + SDL_memset(stream, 0, len); + for(i = 0; i < POLYPHONY; ++i) + apu_render(&apu[i], samples, samples + len / 2); + (void)u; +} + +void +redraw(Uxn *u) +{ + if(debug) + inspect(&ppu, u->wst.dat, u->wst.ptr); + SDL_UpdateTexture(bgTexture, &gRect, ppu.bg.pixels, ppu.width * sizeof(Uint32)); + SDL_UpdateTexture(fgTexture, &gRect, ppu.fg.pixels, ppu.width * sizeof(Uint32)); + SDL_RenderClear(gRenderer); + SDL_RenderCopy(gRenderer, bgTexture, NULL, NULL); + SDL_RenderCopy(gRenderer, fgTexture, NULL, NULL); + SDL_RenderPresent(gRenderer); + reqdraw = 0; +} + +void +toggledebug(Uxn *u) +{ + debug = !debug; + redraw(u); +} + +void +togglezoom(Uxn *u) +{ + zoom = zoom == 3 ? 1 : zoom + 1; + SDL_SetWindowSize(gWindow, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom); + redraw(u); +} + +void +quit(void) +{ + free(ppu.fg.pixels); + free(ppu.bg.pixels); + SDL_UnlockAudioDevice(audio_id); + SDL_DestroyTexture(bgTexture); + bgTexture = NULL; + SDL_DestroyTexture(fgTexture); + fgTexture = NULL; + SDL_DestroyRenderer(gRenderer); + gRenderer = NULL; + SDL_DestroyWindow(gWindow); + gWindow = NULL; + SDL_Quit(); + exit(0); +} + +int +init(void) +{ + SDL_AudioSpec as; + if(!initppu(&ppu, 48, 32)) + return error("PPU", "Init failure"); + gRect.x = PAD; + gRect.y = PAD; + gRect.w = ppu.width; + gRect.h = ppu.height; + if(!initmpu(&mpu, 1)) + return error("MPU", "Init failure"); + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) + return error("Init", SDL_GetError()); + gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, (ppu.width + PAD * 2) * zoom, (ppu.height + PAD * 2) * zoom, SDL_WINDOW_SHOWN); + if(gWindow == NULL) + return error("Window", SDL_GetError()); + gRenderer = SDL_CreateRenderer(gWindow, -1, 0); + if(gRenderer == NULL) + return error("Renderer", SDL_GetError()); + SDL_RenderSetLogicalSize(gRenderer, ppu.width + PAD * 2, ppu.height + PAD * 2); + bgTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2); + if(bgTexture == NULL || SDL_SetTextureBlendMode(bgTexture, SDL_BLENDMODE_NONE)) + return error("Texture", SDL_GetError()); + fgTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, ppu.width + PAD * 2, ppu.height + PAD * 2); + if(fgTexture == NULL || SDL_SetTextureBlendMode(fgTexture, SDL_BLENDMODE_BLEND)) + return error("Texture", SDL_GetError()); + SDL_UpdateTexture(bgTexture, NULL, ppu.bg.pixels, 4); + SDL_UpdateTexture(fgTexture, NULL, ppu.fg.pixels, 4); + SDL_StartTextInput(); + SDL_ShowCursor(SDL_DISABLE); + SDL_zero(as); + as.freq = SAMPLE_FREQUENCY; + as.format = AUDIO_S16; + as.channels = 2; + as.callback = audio_callback; + as.samples = 512; + as.userdata = NULL; + audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); + if(!audio_id) + return error("Audio", SDL_GetError()); + SDL_PauseAudioDevice(audio_id, 0); + return 1; +} + +void +domouse(SDL_Event *event) +{ + Uint8 flag = 0x00; + Uint16 x = clamp(event->motion.x - PAD, 0, ppu.hor * 8 - 1); + Uint16 y = clamp(event->motion.y - PAD, 0, ppu.ver * 8 - 1); + mempoke16(devmouse->dat, 0x2, x); + mempoke16(devmouse->dat, 0x4, y); + devmouse->dat[7] = 0x00; + switch(event->button.button) { + case SDL_BUTTON_LEFT: flag = 0x01; break; + case SDL_BUTTON_RIGHT: flag = 0x10; break; + } + switch(event->type) { + case SDL_MOUSEBUTTONDOWN: + devmouse->dat[6] |= flag; + if(flag == 0x10 && (devmouse->dat[6] & 0x01)) + devmouse->dat[7] = 0x01; + if(flag == 0x01 && (devmouse->dat[6] & 0x10)) + devmouse->dat[7] = 0x10; + break; + case SDL_MOUSEBUTTONUP: + devmouse->dat[6] &= (~flag); + break; + } +} + +void +doctrl(Uxn *u, SDL_Event *event, int z) +{ + Uint8 flag = 0x00; + if(z && event->key.keysym.sym == SDLK_h) { + if(SDL_GetModState() & KMOD_LCTRL) + toggledebug(u); + if(SDL_GetModState() & KMOD_LALT) + togglezoom(u); + } + switch(event->key.keysym.sym) { + case SDLK_LCTRL: flag = 0x01; break; + case SDLK_LALT: flag = 0x02; break; + case SDLK_LSHIFT: flag = 0x04; break; + case SDLK_ESCAPE: flag = 0x08; break; + case SDLK_UP: flag = 0x10; break; + case SDLK_DOWN: flag = 0x20; break; + case SDLK_LEFT: flag = 0x40; break; + case SDLK_RIGHT: flag = 0x80; break; + } + if(flag && z) + devctrl->dat[2] |= flag; + else if(flag) + devctrl->dat[2] &= (~flag); + if(z && event->key.keysym.sym < 20) + devctrl->dat[3] = event->key.keysym.sym; +} + +#pragma mark - Devices + +void +system_talk(Device *d, Uint8 b0, Uint8 w) +{ + if(!w) { + d->dat[0x2] = d->u->wst.ptr; + d->dat[0x3] = d->u->rst.ptr; + } else { + putcolors(&ppu, &d->dat[0x8]); + reqdraw = 1; + } + (void)b0; +} + +void +console_talk(Device *d, Uint8 b0, Uint8 w) +{ + if(!w) return; + switch(b0) { + case 0x8: printf("%c", d->dat[0x8]); break; + case 0x9: printf("0x%02x", d->dat[0x9]); break; + case 0xb: printf("0x%04x", mempeek16(d->dat, 0xa)); break; + case 0xd: printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; + } + fflush(stdout); +} + +void +screen_talk(Device *d, Uint8 b0, Uint8 w) +{ + if(w && b0 == 0xe) { + Uint16 x = mempeek16(d->dat, 0x8); + Uint16 y = mempeek16(d->dat, 0xa); + Uint8 *addr = &d->mem[mempeek16(d->dat, 0xc)]; + Layer *layer = d->dat[0xe] >> 4 & 0x1 ? &ppu.fg : &ppu.bg; + Uint8 mode = d->dat[0xe] >> 5; + if(!mode) + putpixel(&ppu, layer, x, y, d->dat[0xe] & 0x3); + else if(mode-- & 0x1) + puticn(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); + else + putchr(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); + reqdraw = 1; + } +} + +void +file_talk(Device *d, Uint8 b0, Uint8 w) +{ + Uint8 read = b0 == 0xd; + if(w && (read || b0 == 0xf)) { + char *name = (char *)&d->mem[mempeek16(d->dat, 0x8)]; + Uint16 result = 0, length = mempeek16(d->dat, 0xa); + Uint16 offset = mempeek16(d->dat, 0x4); + Uint16 addr = mempeek16(d->dat, b0 - 1); + FILE *f = fopen(name, read ? "r" : (offset ? "a" : "w")); + if(f) { + printf("%s %04x %s %s: ", read ? "Loading" : "Saving", addr, read ? "from" : "to", name); + if(fseek(f, offset, SEEK_SET) != -1) + result = read ? fread(&d->mem[addr], 1, length, f) : fwrite(&d->mem[addr], 1, length, f); + printf("%04x bytes\n", result); + fclose(f); + } + mempoke16(d->dat, 0x2, result); + } +} + +static void +audio_talk(Device *d, Uint8 b0, Uint8 w) +{ + Apu *c = &apu[d - devaudio0]; + if(!w) { + if(b0 == 0x2) + mempoke16(d->dat, 0x2, c->i); + else if(b0 == 0x4) + d->dat[0x4] = apu_get_vu(c); + } else if(b0 == 0xf) { + SDL_LockAudioDevice(audio_id); + c->len = mempeek16(d->dat, 0xa); + c->addr = &d->mem[mempeek16(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, mempeek16(d->dat, 0x8), d->dat[0xf] & 0x7f); + SDL_UnlockAudioDevice(audio_id); + } +} + +void +datetime_talk(Device *d, Uint8 b0, Uint8 w) +{ + time_t seconds = time(NULL); + struct tm *t = localtime(&seconds); + t->tm_year += 1900; + mempoke16(d->dat, 0x0, t->tm_year); + d->dat[0x2] = t->tm_mon; + d->dat[0x3] = t->tm_mday; + d->dat[0x4] = t->tm_hour; + d->dat[0x5] = t->tm_min; + d->dat[0x6] = t->tm_sec; + d->dat[0x7] = t->tm_wday; + mempoke16(d->dat, 0x08, t->tm_yday); + d->dat[0xa] = t->tm_isdst; + (void)b0; + (void)w; +} + +void +midi_talk(Device *d, Uint8 b0, Uint8 w) +{ + (void)d; + (void)b0; + (void)w; +} + +void +nil_talk(Device *d, Uint8 b0, Uint8 w) +{ + (void)d; + (void)b0; + (void)w; +} + +#pragma mark - Generics + +int +start(Uxn *u) +{ + evaluxn(u, 0x0100); + redraw(u); + while(1) { + int i; + SDL_Event event; + double elapsed, start = 0; + if(!bench) + start = SDL_GetPerformanceCounter(); + while(SDL_PollEvent(&event) != 0) { + switch(event.type) { + case SDL_QUIT: + quit(); + break; + case SDL_TEXTINPUT: + case SDL_KEYDOWN: + case SDL_KEYUP: + if(event.text.text[0] >= ' ' && event.text.text[0] <= '~') + devctrl->dat[3] = event.text.text[0]; + doctrl(u, &event, event.type == SDL_KEYDOWN); + evaluxn(u, mempeek16(devctrl->dat, 0)); + devctrl->dat[3] = 0; + break; + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEMOTION: + domouse(&event); + evaluxn(u, mempeek16(devmouse->dat, 0)); + break; + case SDL_WINDOWEVENT: + if(event.window.event == SDL_WINDOWEVENT_EXPOSED) + redraw(u); + break; + } + } + listenmpu(&mpu); + for(i = 0; i < mpu.queue; ++i) { + devmidi->dat[2] = mpu.events[i].message; + devmidi->dat[3] = mpu.events[i].message >> 8; + devmidi->dat[4] = mpu.events[i].message >> 16; + evaluxn(u, mempeek16(devmidi->dat, 0)); + } + evaluxn(u, mempeek16(devscreen->dat, 0)); + if(reqdraw) + redraw(u); + if(!bench) { + elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0f; + SDL_Delay(clamp(16.666f - elapsed, 0, 1000)); + } + } + return 1; +} + +int +main(int argc, char **argv) +{ + Uxn u; + zoom = 2; + + if(argc < 2) + return error("Input", "Missing"); + if(!bootuxn(&u)) + return error("Boot", "Failed"); + if(!loaduxn(&u, argv[1])) + return error("Load", "Failed"); + if(!init()) + return error("Init", "Failed"); + + portuxn(&u, 0x0, "system", system_talk); + portuxn(&u, 0x1, "console", console_talk); + devscreen = portuxn(&u, 0x2, "screen", screen_talk); + devaudio0 = portuxn(&u, 0x3, "audio0", audio_talk); + portuxn(&u, 0x4, "audio1", audio_talk); + portuxn(&u, 0x5, "audio2", audio_talk); + portuxn(&u, 0x6, "audio3", audio_talk); + devmidi = portuxn(&u, 0x7, "midi", midi_talk); + devctrl = portuxn(&u, 0x8, "controller", nil_talk); + devmouse = portuxn(&u, 0x9, "mouse", nil_talk); + portuxn(&u, 0xa, "file", file_talk); + portuxn(&u, 0xb, "datetime", datetime_talk); + portuxn(&u, 0xc, "---", nil_talk); + portuxn(&u, 0xd, "---", nil_talk); + portuxn(&u, 0xe, "---", nil_talk); + portuxn(&u, 0xf, "---", nil_talk); + + /* Write screen size to dev/screen */ + mempoke16(devscreen->dat, 2, ppu.hor * 8); + mempoke16(devscreen->dat, 4, ppu.ver * 8); + + start(&u); + quit(); + return 0; +}