├── .github └── FUNDING.yml ├── app.h ├── .gitignore ├── asmmain.c ├── dasmmain.c ├── emscripten.mak ├── examples ├── cube.asm ├── syntax.asm └── font2.asm ├── sdl ├── pocadv.h └── pocadv.c ├── Makefile ├── gdi ├── gdi.h └── gdi.c ├── chip8.html ├── c8dasm.c ├── LICENSE ├── README.md ├── chip8.h ├── render.c ├── chip8.c └── d.awk /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: wernsey 2 | -------------------------------------------------------------------------------- /app.h: -------------------------------------------------------------------------------- 1 | #define WINDOW_CAPTION "CHIP-8 Machine" 2 | 3 | #define USE_LOG_STREAM 1 4 | #define LOG_STREAM stderr 5 | 6 | #define EPX_SCALE 0 7 | #define SCREEN_SCALE 4 8 | 9 | #define SCREEN_WIDTH 128 10 | #define SCREEN_HEIGHT 64 11 | 12 | /* Set to one to make ESC quit the game - when debugging */ 13 | #define ESCAPE_QUITS 1 14 | 15 | #define CRT_NOISE 0 16 | #define CRT_BLUR 0 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | 3 | # Executables: 4 | *.exe 5 | c8asm 6 | chip8 7 | c8dasm 8 | 9 | # emscripten generated code 10 | out 11 | 12 | # Log files, screenshots... 13 | *.log 14 | screen-*.bmp 15 | 16 | # Backup files 17 | *.bak 18 | *~ 19 | test*.asm 20 | 21 | # default assembler output file 22 | a.ch8 23 | 24 | .gdbinit 25 | 26 | # Generated documentation: 27 | chip8-api.html 28 | assembler.html 29 | README.html 30 | 31 | # http://devernay.free.fr/hacks/chip8/GAMES.zip 32 | GAMES 33 | roms 34 | tests 35 | 36 | .vscode 37 | *.ch8 38 | -------------------------------------------------------------------------------- /asmmain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "chip8.h" 9 | 10 | static void usage(const char *name) { 11 | printf("usage: %s [options] infile.asm\n", name); 12 | printf("where options are:\n"); 13 | printf(" -o outfile : Output file\n"); 14 | printf(" -v : Verbose mode\n"); 15 | } 16 | 17 | int main(int argc, char *argv[]) { 18 | int opt; 19 | const char *infile = NULL; 20 | const char *outfile = "a.ch8"; 21 | char *text; 22 | 23 | while((opt = getopt(argc, argv, "vo:?")) != -1) { 24 | switch(opt) { 25 | case 'v': { 26 | c8_verbose++; 27 | } break; 28 | case 'o': { 29 | outfile = optarg; 30 | } break; 31 | case '?' : { 32 | usage(argv[0]); 33 | return 1; 34 | } 35 | } 36 | } 37 | if(optind >= argc) { 38 | usage(argv[0]); 39 | return 1; 40 | } 41 | infile = argv[optind++]; 42 | 43 | if(c8_verbose) 44 | printf("Reading input from '%s'...\n", infile); 45 | 46 | text = c8_load_txt(infile); 47 | if(!text) { 48 | fprintf(stderr, "error: unable to read '%s'\n", infile); 49 | return 1; 50 | } 51 | 52 | if(c8_verbose) 53 | printf("Input read.\n"); 54 | 55 | c8_reset(); 56 | 57 | int return_code = c8_assemble(text); 58 | 59 | if(c8_verbose) 60 | printf("Writing output to '%s'...\n", outfile); 61 | if(!c8_save_file(outfile)) { 62 | fprintf(stderr, "error: unable to write output to '%s': %s\n", outfile, strerror(errno)); 63 | return 1; 64 | } 65 | if(c8_verbose) 66 | printf("Output written.\n"); 67 | 68 | free(text); 69 | if(c8_verbose) 70 | printf("Success.\n"); 71 | 72 | return return_code; 73 | } 74 | -------------------------------------------------------------------------------- /dasmmain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "chip8.h" 7 | 8 | static void usage(const char *name) { 9 | printf("usage: %s [options] infile.bin\n", name); 10 | printf("where options are:\n"); 11 | printf(" -d : Dump bytes\n"); 12 | printf(" -a : Dump bytes with addresses\n"); 13 | printf(" -r address : Marks `address` as reachable\n"); 14 | printf(" -v : Verbose mode\n"); 15 | } 16 | 17 | int main(int argc, char *argv[]) { 18 | 19 | int opt, dump = 0; 20 | const char *infile = NULL; 21 | 22 | c8_reset(); 23 | c8_disasm_start(); 24 | 25 | while((opt = getopt(argc, argv, "vdar:?")) != -1) { 26 | switch(opt) { 27 | case 'd': dump = 1; break; 28 | case 'a': dump = 2; break; 29 | case 'v': { 30 | c8_verbose++; 31 | } break; 32 | case 'r': { 33 | uint16_t addr = strtol(optarg, NULL, 0); 34 | if(addr > TOTAL_RAM) 35 | fprintf(stderr, "error: Invalid address #%04X\n", addr); 36 | c8_disasm_reachable(addr); 37 | } break; 38 | case '?' : { 39 | usage(argv[0]); 40 | return 1; 41 | } 42 | } 43 | } 44 | if(optind >= argc) { 45 | usage(argv[0]); 46 | return 1; 47 | } 48 | infile = argv[optind++]; 49 | 50 | 51 | if(!c8_load_file(infile)) { 52 | fprintf(stderr, "error: Unable to load %s\n", infile); 53 | return 1; 54 | } 55 | 56 | if(dump == 0) { 57 | c8_disasm(); 58 | } else { 59 | uint16_t pc; 60 | for(pc = PROG_OFFSET; pc < c8_prog_size(); pc += 2) { 61 | uint16_t op = c8_opcode(pc); 62 | if(dump == 2) { 63 | c8_message("%03X: %04X\n", pc, op); 64 | } else { 65 | c8_message("%04X\n", op); 66 | } 67 | } 68 | } 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /emscripten.mak: -------------------------------------------------------------------------------- 1 | # Makefile for emscripten 2 | 3 | # The command 4 | # $ make -f emscripten.mak run 5 | # 6 | # will run `python3 -m http.server` 7 | # 8 | # You can then point your browser to 9 | # http://localhost:8080/chip8.html 10 | 11 | CC=emcc 12 | 13 | # `-s NO_EXIT_RUNTIME=0` is because of the call to `exit()` in `exit_error()` 14 | CFLAGS=-c -I. -Werror -Wall -DABGR=1 15 | #LDFLAGS= -s WASM=0 -s NO_EXIT_RUNTIME=0 16 | LDFLAGS= -s NO_EXIT_RUNTIME=0 17 | 18 | SOURCES=sdl/pocadv.c render.c chip8.c bmp.c 19 | OBJECTS=$(SOURCES:.c=.o) 20 | 21 | OUTDIR=out 22 | EXECUTABLE=$(OUTDIR)/chip8.js 23 | 24 | ifeq ($(BUILD),debug) 25 | # Debug 26 | CFLAGS += -O0 -g 27 | LDFLAGS += 28 | else 29 | # Release mode 30 | CFLAGS += -O2 -DNDEBUG 31 | LDFLAGS += 32 | endif 33 | 34 | all: $(EXECUTABLE) 35 | 36 | # http://stackoverflow.com/a/1400184/115589 37 | THIS_FILE = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) 38 | 39 | debug: 40 | make -f $(THIS_FILE) BUILD=debug 41 | 42 | $(OUTDIR): 43 | -mkdir -p $(OUTDIR) 44 | -mkdir -p $(OUTDIR)/GAMES 45 | -cp -r GAMES/*.ch8 $(OUTDIR)/GAMES 46 | -cp -r examples/*.ch8 $(OUTDIR)/GAMES 47 | 48 | $(EXECUTABLE): $(OUTDIR) $(OBJECTS) $(OUTDIR)/chip8.html 49 | $(CC) $(OBJECTS) $(LDFLAGS) -o $@ 50 | 51 | $(OUTDIR)/chip8.html: chip8.html 52 | cp chip8.html $(OUTDIR) 53 | 54 | .c.o: 55 | $(CC) $(CFLAGS) $< -o $@ 56 | 57 | pocadv.o: sdl/pocadv.c sdl/pocadv.h sdl/../app.h sdl/../bmp.h 58 | render.o: render.c sdl/pocadv.h sdl/../app.h sdl/../bmp.h chip8.h bmp.h 59 | chip8.o: chip8.c chip8.h 60 | bmp.o: bmp.c bmp.h 61 | 62 | .PHONY : clean run deps 63 | 64 | deps: $(SOURCES) 65 | $(CC) -MM $(SOURCES) 66 | 67 | run: $(EXECUTABLE) 68 | -cd $(OUTDIR) && python3 -m http.server 69 | 70 | clean: 71 | -rm -f *.o sdl/*.o 72 | -rm -rf $(OUTDIR) 73 | 74 | -------------------------------------------------------------------------------- /examples/cube.asm: -------------------------------------------------------------------------------- 1 | ; Companion Cube Sample program. Disregard its advice. 2 | 3 | ; Assemble the program to a CHIP-8 binary like so: 4 | ; $ ./c8asm -o GAMES/CUBE8.ch8 examples/cube.asm 5 | ; Once you've assembled it, you can run it in the interpreter like so: 6 | ; $ ./chip8 -f FFB6F8 -b B2B2B2 GAMES/CUBE.ch8 7 | 8 | ; Define some more human-friendly names for the registers 9 | define boxx V0 ; Sprite X,Y position 10 | define boxy V1 11 | define oldx V2 ; Previous sprite X,Y position 12 | define oldy V3 13 | define dirx V4 ; Sprite direction 14 | define diry V5 15 | define tmp VE 16 | 17 | ; Clear the screen 18 | CLS 19 | 20 | SYS 1 ; SYS instructions are treated as no-ops 21 | 22 | ; Load the variables' initial values 23 | LD boxx, 1 24 | LD dirx, 1 25 | LD boxy, 10 26 | LD I, sprite1 27 | DRW boxx, boxy, 8 28 | LD tmp, 1 29 | 30 | ; The main loop 31 | loop: 32 | ; Store the current position 33 | LD oldx, boxx 34 | LD oldy, boxy 35 | 36 | ; If the X direction is 0, go to sub1... 37 | SE dirx, 0 38 | JP sub1 39 | 40 | ; ...otherwise add 1 to the box' position 41 | ADD boxx, 1 42 | 43 | ; If you reached the right edge of the screen, change direction 44 | SNE boxx, 56 45 | LD dirx, 1 46 | jp draw1 47 | sub1: 48 | ; subtract 1 from the box' position 49 | SUB boxx, tmp 50 | 51 | ; If you reached the left edge of the screen, change direction 52 | SNE boxx, 0 53 | LD dirx, 0 54 | 55 | ; Draw the box 56 | draw1: 57 | ; Load the address of the sprite's graphics into register I 58 | LD I, sprite1 59 | ; Erase the sprite at the old position 60 | DRW oldx, oldy, 8 61 | ; Draw the sprite at the new position 62 | DRW boxx, boxy, 8 63 | 64 | ; Return to the start of the loop 65 | JP loop 66 | 67 | ; Binary data of the sprite. 68 | ; 1s represent pixels to be drawn, 0s are blank pixels. 69 | sprite1: 70 | db %01111110, 71 | %10000001, 72 | %10100101, 73 | %10111101, 74 | %10111101, 75 | %10011001, 76 | %10000001, 77 | %01111110, 78 | -------------------------------------------------------------------------------- /examples/syntax.asm: -------------------------------------------------------------------------------- 1 | ; This program just demonstrates and tests all the instructions in the assembler. 2 | ; Don't try to run it. It will probably do strange things. 3 | 4 | ; Comments start with semicolons 5 | start: ; labels are identifiers followed by colons 6 | CLS 7 | JP start 8 | JP #123 ; Hexadecimal numbers are preceded by # symbols 9 | JP v0, #123 10 | JP v0, end 11 | call end 12 | call #203 13 | se V1, #AA 14 | se V2, v3 15 | sne V1, #AA 16 | sne V2, v3 17 | end: 18 | RET 19 | add V1, #AA 20 | add V2, v3 21 | ld V1, #AA 22 | ld V2, v3 23 | or V2, v3 24 | and VA, vb 25 | xor VA, vb 26 | shr VA, vb 27 | shr VA 28 | subn VA, vb 29 | shl VA, vb 30 | shl VA 31 | rnd VD, #FF 32 | drw VE, VF, #4 33 | skp VE 34 | sknp VA 35 | add I, V8 36 | ld I, #AAA 37 | ld V5, DT 38 | ld V5, K 39 | ld DT, V5 40 | ld ST, V5 41 | ld F, V5 42 | ld B, V5 43 | ld [I], VA 44 | ld VA, [I] 45 | 46 | ; "define" can be used to define constants 47 | define aaa #222 48 | jp aaa 49 | 50 | ; "define" can also be used to define aliases for registers 51 | define bbb vd 52 | ld bbb, %01010101 ; Binary literals start with % symbols 53 | JP %101001010101 54 | JP x 55 | LD I, x 56 | 57 | ; SCHIP instructions are supported 58 | SCD #4 59 | SCL 60 | SCR 61 | EXIT 62 | HIGH 63 | LOW 64 | DRW V1, V2, 0 65 | LD HF, V5 66 | LD R, V6 67 | LD V7, R 68 | 69 | ; Offset moves the location where output is generated 70 | offset #280 71 | 72 | ; This is how you can define sprites: 73 | ; "db" emits raw bytes, separated by commas. 74 | ; "dw" can emit 16-bit words. 75 | 76 | x: db #11, #22, #33, #44 77 | y: db 78 | %00100100, 79 | %11111111, 80 | %01011010, 81 | %00111100, 82 | %00100100 83 | CLS 84 | 85 | ; You can load text data into the output through the `text` directive: 86 | string1: 87 | text "hello" 88 | ; (The label `string1` can be used later like so: `ld I, string1`) 89 | 90 | ; The above is equivalent to this `db` directive 91 | ; db #68, #65, #6C, #6C, #6F, #00 92 | 93 | ; The string can also contain special symbols escaped with '\' 94 | string2: text "\"\e\r\n" 95 | 96 | ; This is how you can include another file: 97 | include "font2.asm" 98 | -------------------------------------------------------------------------------- /sdl/pocadv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Pocket Adventure 3 | * ================ 4 | * 5 | * The simplest SDL wrapper framework I could get away with. 6 | */ 7 | #if defined(ANDROID) || defined(_MSC_VER) 8 | # include 9 | # ifndef SDL2 10 | # define SDL2 11 | # endif 12 | #else 13 | # ifdef SDL2 14 | # include 15 | # else 16 | # include 17 | # endif 18 | #endif 19 | 20 | #ifdef SDL2 21 | # define FILEOBJ SDL_RWops 22 | # define FOPEN SDL_RWFromFile 23 | # define FCLOSE SDL_RWclose 24 | # define FSEEK(ctx, offs, orig) SDL_RWseek(ctx, offs, RW_ ## orig) 25 | # define FTELL SDL_RWtell 26 | # define FREAD(ptr, size, num, ctx) SDL_RWread(ctx, ptr, size, num) 27 | # define REWIND(ctx) SDL_RWseek(ctx, 0, RW_SEEK_SET) 28 | #else 29 | # define FILEOBJ FILE 30 | # define FOPEN fopen 31 | # define FCLOSE fclose 32 | # define FSEEK fseek 33 | # define FTELL ftell 34 | # define FREAD fread 35 | # define REWIND rewind 36 | #endif 37 | 38 | #ifdef __EMSCRIPTEN__ 39 | #include 40 | #endif 41 | 42 | #include "../app.h" 43 | 44 | #include "../bmp.h" 45 | 46 | #define MIN(a,b) ((a)<(b)?(a):(b)) 47 | #define MAX(a,b) ((a)>(b)?(a):(b)) 48 | 49 | extern Bitmap *screen; 50 | 51 | #ifndef SDL2 52 | extern char keys[SDLK_LAST]; 53 | #define KCODE(k) SDLK_ ## k 54 | #define KCODEA(k,K) SDLK_ ## k 55 | #else 56 | extern char keys[SDL_NUM_SCANCODES]; 57 | #define KCODE(k) SDL_SCANCODE_ ## k 58 | #define KCODEA(k,K) SDL_SCANCODE_ ## K 59 | #endif 60 | 61 | extern int quit; 62 | 63 | extern int show_debug(); 64 | 65 | extern int key_pressed(); 66 | 67 | extern int mouse_clicked(); 68 | extern int mouse_released(); 69 | extern int mouse_down(); 70 | extern int mouse_moved(); 71 | extern int mouse_x, mouse_y; 72 | 73 | extern void set_cursor(Bitmap *b, int hsx, int hsy); 74 | 75 | extern void rlog(const char *fmt, ...); 76 | 77 | extern void rerror(const char *fmt, ...); 78 | 79 | extern void exit_error(const char *msg, ...); 80 | 81 | extern char *readfile(const char *fname); 82 | 83 | extern Bitmap *get_bmp(const char *filename); 84 | 85 | /* These functions should be provided elsewhere */ 86 | extern void init_game(int argc, char *argv[]); 87 | extern void deinit_game(); 88 | extern int render(double deltaTime); 89 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Linux and Windows (MinGW) 2 | CC=gcc 3 | CFLAGS=-c -Wall 4 | LDFLAGS=-lm 5 | 6 | # Detect operating system: 7 | # More info: http://stackoverflow.com/q/714100 8 | ifeq ($(OS),Windows_NT) 9 | EXECUTABLES=chip8 chip8-gdi 10 | LDFLAGS+=-mwindows 11 | else 12 | EXECUTABLES=chip8 13 | endif 14 | 15 | ifeq ($(BUILD),debug) 16 | # Debug 17 | CFLAGS += -O0 -g 18 | LDFLAGS += 19 | else 20 | # Release mode 21 | CFLAGS += -O2 -DNDEBUG 22 | LDFLAGS += -s 23 | endif 24 | 25 | all: c8asm c8dasm $(EXECUTABLES) docs example 26 | 27 | debug: 28 | make BUILD=debug 29 | 30 | c8asm: asmmain.o c8asm.o chip8.o 31 | $(CC) $(LDFLAGS) -o $@ $^ 32 | 33 | c8dasm: dasmmain.o c8dasm.o chip8.o 34 | $(CC) $(LDFLAGS) -o $@ $^ 35 | 36 | .c.o: 37 | $(CC) $(CFLAGS) $< -o $@ 38 | 39 | asmmain.o: asmmain.c chip8.h 40 | bmp.o: bmp.c bmp.h 41 | c8asm.o: c8asm.c chip8.h 42 | c8dasm.o: c8dasm.c chip8.h 43 | chip8.o: chip8.c chip8.h 44 | dasmmain.o: dasmmain.c chip8.h 45 | render.o: render.c gdi/gdi.h gdi/../bmp.h gdi/../app.h chip8.h bmp.h 46 | gdi.o: gdi/gdi.c gdi/../bmp.h gdi/gdi.h gdi/../app.h 47 | pocadv.o: sdl/pocadv.c sdl/pocadv.h sdl/../app.h sdl/../bmp.h 48 | 49 | # SDL specific: 50 | chip8: pocadv.o render-sdl.o chip8.o bmp.o 51 | $(CC) $^ $(LDFLAGS) `sdl2-config --libs` -o $@ 52 | render-sdl.o: render.c chip8.h sdl/pocadv.h app.h bmp.h 53 | $(CC) $(CFLAGS) -DSDL2 `sdl2-config --cflags` $< -o $@ 54 | pocadv.o: sdl/pocadv.c sdl/pocadv.h app.h bmp.h 55 | $(CC) $(CFLAGS) -DSDL2 `sdl2-config --cflags` $< -o $@ 56 | 57 | # Example 58 | example : examples/CUBE8.ch8 59 | examples/CUBE8.ch8 : examples/cube.asm ./c8asm 60 | mkdir -p GAMES 61 | ./c8asm -o $@ $< 62 | 63 | # Windows GDI-version specific: 64 | chip8-gdi: gdi.o render-gdi.o chip8.o bmp.o 65 | $(CC) $^ -o $@ $(LDFLAGS) 66 | render-gdi.o: render.c chip8.h gdi/gdi.h app.h bmp.h 67 | $(CC) $(CFLAGS) -DGDI $< -o $@ 68 | gdi.o: gdi/gdi.c gdi/gdi.h app.h bmp.h 69 | $(CC) $(CFLAGS) -DGDI $< -o $@ 70 | 71 | # Documentation 72 | docs: chip8-api.html assembler.html README.html 73 | 74 | chip8-api.html: chip8.h d.awk 75 | awk -f d.awk $< > $@ 76 | 77 | assembler.html: c8asm.c d.awk 78 | awk -f d.awk $< > $@ 79 | 80 | README.html: README.md d.awk 81 | awk -f d.awk -v Clean=1 $< > $@ 82 | 83 | .PHONY : clean wipe 84 | 85 | wipe: 86 | -rm -f *.o sdl/*.o gdi/*.o 87 | 88 | clean: wipe 89 | -rm -f c8asm chip8 c8dasm *.exe 90 | -rm -f chip8-api.html README.html 91 | -rm -f *.log *.bak 92 | -------------------------------------------------------------------------------- /gdi/gdi.h: -------------------------------------------------------------------------------- 1 | #include "../bmp.h" 2 | 3 | #include "../app.h" 4 | 5 | #define FPS 33 6 | 7 | /* You can find the values for various keys in winuser.h 8 | (they all start with VK_*) 9 | Don't use them directly; Rather use the KCODE(k) macro below. 10 | */ 11 | #define KEY_ESCAPE 0x1B 12 | #define KEY_SPACE 0x20 13 | #define KEY_LEFT 0x25 14 | #define KEY_UP 0x26 15 | #define KEY_RIGHT 0x27 16 | #define KEY_DOWN 0x28 17 | 18 | #define KEY_SHIFT 0x10 19 | #define KEY_LSHIFT 0xA0 20 | #define KEY_RSHIFT 0xA1 21 | #define KEY_CTRL 0x11 22 | 23 | #define KEY_HOME 0x24 24 | #define KEY_END 0x23 25 | #define KEY_BKSP 0x08 26 | #define KEY_DELETE 0x2E 27 | #define KEY_PAGEDOWN 0x22 28 | #define KEY_PAGEUP 0x21 29 | 30 | #define KEY_F1 0x70 31 | #define KEY_F2 0x71 32 | #define KEY_F3 0x72 33 | #define KEY_F4 0x73 34 | #define KEY_F5 0x74 35 | #define KEY_F6 0x75 36 | #define KEY_F7 0x76 37 | #define KEY_F8 0x77 38 | #define KEY_F9 0x78 39 | #define KEY_F10 0x79 40 | #define KEY_F11 0x7A 41 | #define KEY_F12 0x7B 42 | 43 | #define KEY_a 0x41 44 | #define KEY_b 0x42 45 | #define KEY_c 0x43 46 | #define KEY_d 0x44 47 | #define KEY_e 0x45 48 | #define KEY_f 0x46 49 | #define KEY_g 0x47 50 | #define KEY_h 0x48 51 | #define KEY_i 0x49 52 | #define KEY_j 0x4A 53 | #define KEY_k 0x4B 54 | #define KEY_l 0x4C 55 | #define KEY_m 0x4D 56 | #define KEY_n 0x4E 57 | #define KEY_o 0x4F 58 | #define KEY_p 0x50 59 | #define KEY_q 0x51 60 | #define KEY_r 0x52 61 | #define KEY_s 0x53 62 | #define KEY_t 0x54 63 | #define KEY_u 0x55 64 | #define KEY_v 0x56 65 | #define KEY_w 0x57 66 | #define KEY_x 0x58 67 | #define KEY_y 0x59 68 | #define KEY_z 0x5A 69 | 70 | #define KEY_LEFTBRACKET 0xDB 71 | #define KEY_RIGHTBRACKET 0xDD 72 | #define KEY_COMMA 0xBC 73 | #define KEY_PERIOD 0xBE 74 | #define KEY_SLASH 0xBF 75 | #define KEY_SEMICOLON 0xBA 76 | #define KEY_BACKSLASH 0xDC 77 | #define KEY_APOSTROPHE 0xDE 78 | 79 | #define KCODE(x) KEY_ ## x 80 | #define KCODEA(x,X) KEY_ ## x 81 | 82 | extern Bitmap *screen; 83 | 84 | /* See the KEY_* defines above */ 85 | extern char keys[]; 86 | 87 | extern int quit; 88 | 89 | extern int show_debug(); 90 | 91 | /* 92 | If the return value < 128 it is an ASCII code, 93 | otherwise it is special. 94 | */ 95 | int key_pressed(); 96 | 97 | extern int mouse_clicked(); 98 | extern int mouse_released(); 99 | extern int mouse_down(); 100 | extern int mouse_moved(); 101 | extern int mouse_x, mouse_y; 102 | 103 | extern void set_cursor(Bitmap *b, int hsx, int hsy); 104 | 105 | extern void rlog(const char *fmt, ...); 106 | 107 | extern void rerror(const char *fmt, ...); 108 | 109 | extern void exit_error(const char *msg, ...); 110 | 111 | extern char *readfile(const char *fname); 112 | 113 | extern Bitmap *get_bmp(const char *filename); 114 | 115 | /* These functions should be provided elsewhere */ 116 | extern void init_game(int argc, char *argv[]); 117 | extern void deinit_game(); 118 | extern int render(double elapsedSeconds); 119 | -------------------------------------------------------------------------------- /examples/font2.asm: -------------------------------------------------------------------------------- 1 | ; This is the 8*10 font for SCHIP. 2 | ; Run it through the assembler to get the 3 | ; hex codes for the fonts that you can copy 4 | ; and paste into chip8.c 5 | db ; '0' 6 | %01111100, 7 | %10000010, 8 | %10000010, 9 | %10000010, 10 | %10000010, 11 | %10000010, 12 | %10000010, 13 | %10000010, 14 | %01111100, 15 | %00000000 16 | 17 | db ; '1' 18 | %00001000, 19 | %00011000, 20 | %00111000, 21 | %00001000, 22 | %00001000, 23 | %00001000, 24 | %00001000, 25 | %00001000, 26 | %00111100, 27 | %00000000 28 | 29 | db ; '2' 30 | %01111100, 31 | %10000010, 32 | %00000010, 33 | %00000010, 34 | %00000100, 35 | %00011000, 36 | %00100000, 37 | %01000000, 38 | %11111110, 39 | %00000000 40 | 41 | db ; '3' 42 | %01111100, 43 | %10000010, 44 | %00000010, 45 | %00000010, 46 | %00111100, 47 | %00000010, 48 | %00000010, 49 | %10000010, 50 | %01111100, 51 | %00000000 52 | 53 | db ; '4' 54 | %10000100, 55 | %10000100, 56 | %10000100, 57 | %10000100, 58 | %11111110, 59 | %00000100, 60 | %00000100, 61 | %00000100, 62 | %00000100, 63 | %00000000 64 | 65 | db ; '5' 66 | %11111110, 67 | %10000000, 68 | %10000000, 69 | %10000000, 70 | %11111100, 71 | %00000010, 72 | %00000010, 73 | %10000010, 74 | %01111100, 75 | %00000000 76 | 77 | db ; '6' 78 | %01111100, 79 | %10000010, 80 | %10000000, 81 | %10000000, 82 | %11111100, 83 | %10000010, 84 | %10000010, 85 | %10000010, 86 | %01111100, 87 | %00000000 88 | 89 | db ; '7' 90 | %11111110, 91 | %00000010, 92 | %00000100, 93 | %00001000, 94 | %00010000, 95 | %00100000, 96 | %00100000, 97 | %00100000, 98 | %00100000, 99 | %00000000 100 | 101 | db ; '8' 102 | %01111100, 103 | %10000010, 104 | %10000010, 105 | %10000010, 106 | %01111100, 107 | %10000010, 108 | %10000010, 109 | %10000010, 110 | %01111100, 111 | %00000000 112 | 113 | db ; '9' 114 | %01111100, 115 | %10000010, 116 | %10000010, 117 | %10000010, 118 | %01111110, 119 | %00000010, 120 | %00000010, 121 | %10000010, 122 | %01111100, 123 | %00000000 124 | 125 | db ; 'A' 126 | %00010000, 127 | %00101000, 128 | %01000100, 129 | %10000010, 130 | %10000010, 131 | %11111110, 132 | %10000010, 133 | %10000010, 134 | %10000010, 135 | %00000000 136 | 137 | db ; 'B' 138 | %11111100, 139 | %10000010, 140 | %10000010, 141 | %10000010, 142 | %11111100, 143 | %10000010, 144 | %10000010, 145 | %10000010, 146 | %11111100, 147 | %00000000 148 | 149 | db ; 'C' 150 | %01111100, 151 | %10000010, 152 | %10000000, 153 | %10000000, 154 | %10000000, 155 | %10000000, 156 | %10000000, 157 | %10000010, 158 | %01111100, 159 | %00000000 160 | 161 | db ; 'D' 162 | %11111100, 163 | %10000010, 164 | %10000010, 165 | %10000010, 166 | %10000010, 167 | %10000010, 168 | %10000010, 169 | %10000010, 170 | %11111100, 171 | %00000000 172 | 173 | db ; 'E' 174 | %11111110, 175 | %10000000, 176 | %10000000, 177 | %10000000, 178 | %11111000, 179 | %10000000, 180 | %10000000, 181 | %10000000, 182 | %11111110, 183 | %00000000 184 | 185 | db ; 'F' 186 | %11111110, 187 | %10000000, 188 | %10000000, 189 | %10000000, 190 | %11111000, 191 | %10000000, 192 | %10000000, 193 | %10000000, 194 | %10000000, 195 | %00000000 196 | 197 | -------------------------------------------------------------------------------- /chip8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | CHIP-8 Emulator Wrapper 14 | 124 | 125 | 126 | 127 |
128 | Resize canvas 129 | Lock/hide mouse pointer     130 | 132 | Fullscreen 133 | 134 | Output 135 | Help 136 |
137 | 138 |
139 |
Downloading...
140 | 141 |
142 | 143 |
144 | 145 |
146 | 147 |
148 | 149 | 150 | 151 | 170 | 171 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /c8dasm.c: -------------------------------------------------------------------------------- 1 | /* CHIP-8 Disassembler. 2 | 3 | 4 | It choked on the [5-quirks][] test ROM in Timendus's suite: 5 | 6 | To test the `Bxnn`, there's a BE00 at #5AA that will jump to either #E98 or #E9C 7 | depending on the quirk (look for `jumpQuirk` in the original source). 8 | 9 | LD V0, #98 ; 6098 @ 5A6 10 | LD VE, #9C ; 6E9C @ 5A8 11 | JP V0, #E00 ; BE00 @ 5AA 12 | L5AC: db #A5, #D8, #F0, #65 13 | 14 | Either way, both paths jump back to a label `quirks-resume`, which is at #5AC, 15 | right after the BE00. The problem is that the disassembler isn't able to determine 16 | that #E98 and #E9C are reachable, and subsequently it can't tell that #5AC is 17 | reachable and disassemble anything after that. 18 | 19 | So there's now a `-r` command line option that will use the `c8_disasm_reachable()` 20 | function to tell us that an address is reachable. In our example you can use 21 | `-r 0xE98 -r 0xE9C`, which will tell the disassembler that #E98 and #E9C are reachable. 22 | 23 | [5-quirks]: https://github.com/Timendus/chip8-test-suite/raw/main/bin/5-quirks.ch8 24 | 25 | */ 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "chip8.h" 34 | 35 | #define MAX_BRANCHES 256 36 | 37 | /* If you have a run of ZERO_RUNS or more 0x00 bytes in the data output, 38 | just skip it... */ 39 | #define ZERO_RUNS 16 40 | 41 | #define REACHABLE(addr) (reachable[(addr) >> 3] & (1 << ((addr) & 0x07))) 42 | #define SET_REACHABLE(addr) reachable[(addr) >> 3] |= (1 << ((addr) & 0x07)) 43 | 44 | #define TOUCHED(addr) (touched[(addr) >> 3] & (1 << ((addr) & 0x07))) 45 | #define TOUCH(addr) touched[(addr) >> 3] |= (1 << ((addr) & 0x07)) 46 | 47 | #define IS_LABEL(addr) (labels[(addr) >> 3] & (1 << ((addr) & 0x07))) 48 | #define SET_LABEL(addr) labels[(addr) >> 3] |= (1 << ((addr) & 0x07)) 49 | 50 | static uint16_t branches[MAX_BRANCHES], bsp; 51 | uint8_t labels[TOTAL_RAM/8]; 52 | 53 | void c8_disasm_start() { 54 | bsp = 0; 55 | memset(labels, 0, sizeof labels); 56 | } 57 | 58 | void c8_disasm_reachable(uint16_t addr) { 59 | if(addr > TOTAL_RAM) 60 | return; 61 | branches[bsp++] = addr; 62 | SET_LABEL(addr); 63 | } 64 | 65 | void c8_disasm() { 66 | /* Some of the reported errors can conceivably happen 67 | if you try to disassembe a buggy program */ 68 | uint16_t addr, max_addr = 0, run, run_end = 0; 69 | int odata = 0, 70 | out = 0; 71 | 72 | uint8_t reachable[TOTAL_RAM/8]; 73 | uint8_t touched[TOTAL_RAM/8]; 74 | 75 | memset(reachable, 0, sizeof reachable); 76 | memset(touched, 0, sizeof touched); 77 | 78 | /* Step 1: Determine which instructions are reachable. 79 | We run through the program instruction by instruction. 80 | If we encouter a branch, we push one path onto a stack and 81 | continue along the other. We continue until everything is 82 | marked as reachable and/or the stack is empty. 83 | Also, we mark branch destinations as labels to prettify the 84 | output - to have labels instead of adresses. 85 | */ 86 | branches[bsp++] = PROG_OFFSET; 87 | while(bsp > 0) { 88 | addr = branches[--bsp]; 89 | 90 | while(addr < TOTAL_RAM - 1 && !REACHABLE(addr)) { 91 | 92 | SET_REACHABLE(addr); 93 | 94 | uint16_t opcode = c8_opcode(addr); 95 | if(addr < PROG_OFFSET ) { 96 | /* Program ended up where it shouldn't; assumes the RAM is initialised to 0 */ 97 | c8_message("error: bad jump: program at #%03X\n",addr); 98 | return; 99 | } 100 | 101 | addr += 2; 102 | if(addr >= TOTAL_RAM) { 103 | c8_message("error: program overflows RAM\n"); 104 | return; 105 | } 106 | 107 | uint16_t nnn = opcode & 0x0FFF; 108 | 109 | if(opcode == 0x00EE) { /* RET */ 110 | break; 111 | } else if(opcode == 0x00FD) { /* EXIT */ 112 | break; 113 | } else if((opcode & 0xF000) == 0x1000) { /* JP addr */ 114 | addr = nnn; 115 | assert(addr < TOTAL_RAM); 116 | SET_LABEL(addr); 117 | } else if((opcode & 0xF000) == 0x2000) { /* CALL addr */ 118 | if(bsp == MAX_BRANCHES) { 119 | /* Basically, this program is too complex to disassemble, 120 | but you can increase MAX_BRANCHES to see if it helps. */ 121 | c8_message("error: Too many branches to follow (%u)\n", bsp); 122 | return; 123 | } 124 | branches[bsp++] = addr; /* For the RET */ 125 | addr = nnn; 126 | assert(addr < TOTAL_RAM); 127 | SET_LABEL(addr); 128 | } else if((opcode & 0xF000) == 0x3000 || (opcode & 0xF00F) == 0x5000) { /* SE */ 129 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;} 130 | branches[bsp++] = addr + 2; 131 | } else if((opcode & 0xF000) == 0x4000 || (opcode & 0xF00F) == 0x9000) { /* SNE */ 132 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;} 133 | branches[bsp++] = addr + 2; 134 | } else if((opcode & 0xF0FF) == 0xE09E) { /* SKP */ 135 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;} 136 | branches[bsp++] = addr + 2; 137 | } else if((opcode & 0xF0FF) == 0xE0A1) { /* SKNP */ 138 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp); return;} 139 | branches[bsp++] = addr + 2; 140 | } else if((opcode & 0xF000) == 0xB000) { 141 | /* I don't think we can realistically disassemble this, because 142 | we can't know V0 without actually running the program. */ 143 | break; 144 | } else if((opcode & 0xF000) == 0xA000) { 145 | /* Mark the address as touched so that it don't get removed by the code 146 | that hides the long runs of 0x00 bytes. */ 147 | TOUCH(nnn); 148 | } 149 | } 150 | if(addr >= TOTAL_RAM - 1) { 151 | c8_message("error: program overflows RAM\n"); 152 | return; 153 | } 154 | } 155 | 156 | /* Find the largest non-null address so that we don't write a bunch of 157 | unnecessary zeros at the end of our output */ 158 | for(max_addr = TOTAL_RAM - 1; c8_get(max_addr) == 0; max_addr--); 159 | 160 | /* Step 2: Loop through all the reachable instructions and print them. */ 161 | for(addr = PROG_OFFSET; addr < TOTAL_RAM; addr += REACHABLE(addr)?2:1) { 162 | /* The REACHABLE(addr)?2:1 above is to handle non-aligned instructions properly. */ 163 | char buffer[64]; 164 | 165 | if(!REACHABLE(addr)) { 166 | if(addr <= max_addr) { 167 | /* You've reached data that is non-null, but not code either. */ 168 | if(addr < run_end) 169 | continue; 170 | 171 | /* Find out a run of 0x00 bytes that are not code (REACHABLE) and not 172 | data bytes that are referenced by a `LD I,nnn` (`Annn`) instruction (TOUCHED) */ 173 | for(run = addr; run < TOTAL_RAM && !REACHABLE(run) && !TOUCHED(run) && !c8_get(run); run++); 174 | 175 | if(run - addr > ZERO_RUNS) { 176 | if(odata) { 177 | c8_message("\n"); 178 | odata = 0; 179 | } 180 | c8_message(" ; skipped run of %u #00 bytes at #%04X...\n", run - addr, addr); 181 | c8_message("offset #%04X \n", run); 182 | 183 | run_end = run; 184 | continue; 185 | } 186 | 187 | /* Make sure `db` clauses to blocks touched by `LD I, nnn` start on new line: */ 188 | if(TOUCHED(addr) && odata) { 189 | c8_message("\n"); 190 | odata = 0; 191 | } 192 | 193 | if(!odata++) { 194 | c8_message("L%03X: db #%02X", addr, c8_get(addr)); 195 | } else { 196 | c8_message(", #%02X", c8_get(addr)); 197 | if(odata % 4 == 0) { 198 | c8_message("\n"); 199 | odata = 0; 200 | } 201 | } 202 | } 203 | out = 0; 204 | continue; 205 | } 206 | 207 | uint16_t opcode = c8_opcode(addr); 208 | 209 | buffer[0] = '\0'; 210 | 211 | uint8_t x = (opcode >> 8) & 0x0F; 212 | uint8_t y = (opcode >> 4) & 0x0F; 213 | uint8_t nibble = opcode & 0x0F; 214 | uint16_t nnn = opcode & 0x0FFF; 215 | uint8_t kk = opcode & 0xFF; 216 | 217 | switch(opcode & 0xF000) { 218 | case 0x0000: 219 | if(opcode == 0x00E0) sprintf(buffer,"CLS"); 220 | else if(opcode == 0x00EE) sprintf(buffer,"RET"); 221 | else if((opcode & 0xFFF0) == 0x00C0) sprintf(buffer,"SCD %d", nibble); 222 | else if(opcode == 0x00FB) sprintf(buffer,"SCR"); 223 | else if(opcode == 0x00FC) sprintf(buffer,"SCL"); 224 | else if(opcode == 0x00FD) sprintf(buffer,"EXIT"); 225 | else if(opcode == 0x00FE) sprintf(buffer,"LOW"); 226 | else if(opcode == 0x00FF) sprintf(buffer,"HIGH"); 227 | else sprintf(buffer,"SYS #%03X", nnn); 228 | break; 229 | case 0x1000: sprintf(buffer,"JP L%03X", nnn); break; 230 | case 0x2000: sprintf(buffer,"CALL L%03X", nnn); break; 231 | case 0x3000: sprintf(buffer,"SE V%1X, %d", x, kk); break; 232 | case 0x4000: sprintf(buffer,"SNE V%1X, %d", x, kk); break; 233 | case 0x5000: sprintf(buffer,"SE V%1X, V%1X", x, y); break; 234 | case 0x6000: sprintf(buffer,"LD V%1X, %d", x, kk); break; 235 | case 0x7000: sprintf(buffer,"ADD V%1X, %d", x, kk); break; 236 | case 0x8000: { 237 | switch(nibble) { 238 | case 0x0: sprintf(buffer,"LD V%1X, V%1X", x, y); break; 239 | case 0x1: sprintf(buffer,"OR V%1X, V%1X", x, y); break; 240 | case 0x2: sprintf(buffer,"AND V%1X, V%1X", x, y); break; 241 | case 0x3: sprintf(buffer,"XOR V%1X, V%1X", x, y); break; 242 | case 0x4: sprintf(buffer,"ADD V%1X, V%1X", x, y); break; 243 | case 0x5: sprintf(buffer,"SUB V%1X, V%1X", x, y); break; 244 | case 0x7: sprintf(buffer,"SUBN V%1X, V%1X", x, y); break; 245 | case 0x6: 246 | if(x == y) 247 | sprintf(buffer,"SHR V%1X", x); 248 | else 249 | sprintf(buffer,"SHR V%1X, V%1X", x, y); 250 | break; 251 | case 0xE: 252 | if(x == y) 253 | sprintf(buffer,"SHL V%1X", x); 254 | else 255 | sprintf(buffer,"SHL V%1X, V%1X", x, y); 256 | break; 257 | } 258 | } break; 259 | case 0x9000: sprintf(buffer,"SNE V%1X, V%1X", x, y); break; 260 | case 0xA000: 261 | if(nnn < 0x200) 262 | sprintf(buffer,"LD I, #%03X", nnn); 263 | else 264 | sprintf(buffer,"LD I, L%03X", nnn); 265 | break; 266 | case 0xB000: sprintf(buffer,"JP V0, #%03X", nnn); break; 267 | case 0xC000: sprintf(buffer,"RND V%1X, #%02X", x, kk); break; 268 | case 0xD000: sprintf(buffer,"DRW V%1X, V%1X, %d", x, y, nibble); break; 269 | case 0xE000: { 270 | if(kk == 0x9E) { 271 | sprintf(buffer,"SKP V%1X", x); 272 | } else if(kk == 0xA1) { 273 | sprintf(buffer,"SKNP V%1X", x); 274 | } 275 | } break; 276 | case 0xF000: { 277 | switch(kk) { 278 | case 0x07: sprintf(buffer,"LD V%1X, DT", x); break; 279 | case 0x0A: sprintf(buffer,"KEY V%1X", x); break; 280 | case 0x15: sprintf(buffer,"DELAY V%1X", x); break; 281 | case 0x18: sprintf(buffer,"SOUND V%1X", x); break; 282 | case 0x1E: sprintf(buffer,"ADD I, V%1X", x); break; 283 | case 0x29: sprintf(buffer,"HEX V%1X", x); break; 284 | case 0x33: sprintf(buffer,"BCD V%1X", x); break; 285 | case 0x55: sprintf(buffer,"STOR V%1X", x); break; 286 | case 0x65: sprintf(buffer,"RSTR V%1X", x); break; 287 | case 0x30: sprintf(buffer,"HEXX V%1X", x); break; 288 | case 0x75: sprintf(buffer,"STORX V%1X", x); break; 289 | case 0x85: sprintf(buffer,"RSTRX V%1X", x); break; 290 | } 291 | } break; 292 | } 293 | if(!buffer[0]) { 294 | c8_message("error: Disassembler got confused at #%03X\n", addr); 295 | return; 296 | } 297 | if(IS_LABEL(addr) || TOUCHED(addr) || !out) { 298 | if(odata) c8_message("\n"); 299 | c8_message("L%03X: %-20s ; %04X @ %03X\n", addr, buffer, opcode, addr); 300 | } else 301 | c8_message(" %-20s ; %04X @ %03X\n", buffer, opcode, addr); 302 | out = 1; 303 | odata = 0; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHIP-8 Interpreter, Assembler and Disassembler 2 | 3 | This package contains an interpreter for [CHIP-8][wikipedia] as well as a 4 | command-line assembler and disassembler. 5 | 6 | It also supports the SuperChip instructions. 7 | 8 | The syntax of the assembler and disassembler is based on the syntax described 9 | in [Cowgod's Chip-8 Technical Reference v1.0][cowgod], by Thomas P. Greene 10 | 11 | Frédéric Devernay's [SVision-8 website](http://devernay.free.fr/hacks/chip8/) 12 | has a wealth of information. He also has a collection of CHIP-8 games and 13 | programs in his [GAMES.zip](http://devernay.free.fr/hacks/chip8/GAMES.zip). 14 | 15 | ## Compilation and Usage 16 | 17 | * Linux: Type `make` from the shell. 18 | * Windows: The system was built and tested with the 19 | [MinGW](http://www.mingw.org/) tools. To compile it type `make` from the MSYS 20 | shell. 21 | 22 | To use the emulator: 23 | 24 | * Under Linux: Type `./chip8 game.ch8` where game.ch8 is the binary CHIP-8 file. 25 | * Under Windows: Type `chip8 game.ch8` or `chip8-gdi game.ch8` depending on 26 | which of the implementations (see below) you want to use. 27 | 28 | The assembler and disassemblers are simple command line applications and 29 | platform independent. 30 | 31 | To use the assembler, type 32 | 33 | $ ./c8asm -o file.c8h file.asm 34 | 35 | This will assemble `file.asm` into a binary `file.c8h`. If the `-o` is not 36 | specified it will default to `a.c8h`. 37 | 38 | To use the disassembler, run the command 39 | 40 | $ ./c8dasm a.ch8 > outfile.asm 41 | 42 | where `a.ch8` is the file you want to disassemble. 43 | 44 | ## Interpreter Implementations 45 | 46 | The core of the emulator is in `chip8.c`. The idea is that this core be 47 | platform independent and then hooks are provided for platform specific 48 | implementations. 49 | 50 | The API is described in `chip8.h`. The `docs` target in the Makefile generates 51 | HTML documentation from it. 52 | 53 | Two implementations are provided in this repository: 54 | 55 | 1. A SDL-based implentation () which is intended for 56 | portability, and 57 | 2. a native Windows implementation which is intended for small size and 58 | requires no third party dependencies. 59 | 60 | In both versions 61 | 62 | * `bmp.h` and `bmp.c` (together with the `fonts/` directory) is used to draw 63 | and manipulate the bitmap graphics. See also 64 | https://github.com/wernsey/bitmap 65 | * `render.c` implements the `init_game()`, `deinit_game()` and `render()` 66 | functions that forms the core of both implementations and demonstrates how 67 | the interpreter's API works. 68 | 69 | The `render()` function checks the keyboard and executes the interpreter a 70 | couple of times by calling `c8_step()` and redraws the screen if it changed. 71 | The SDL and Win32 frameworks were written in such a way that the `render()` 72 | function works with both with only a couple of minor modifications. 73 | 74 | The implementations feature a rudimentary debugger: Press F5 to pause a running 75 | game. The program counter and the current instruction will be displayed at the 76 | bottom of the screen, along with the values of the 16 Vx registers. Press F6 to 77 | step through the program to the next instruction and F8 to resume the program. 78 | 79 | The `Makefile` will build the SDL version by default, and build the GDI version 80 | under Windows. 81 | 82 | ### SDL Implementation 83 | 84 | The SDL-based implementation is intended for portability. The files `pocadv.c` 85 | and `pocadv.h` implement a wrapper around the SDL that contains the `main()` 86 | function, the SDL event loops and so on. 87 | 88 | The included `emscripten.mak` file is used to compile the SDL implementation to 89 | JavaScript with [Emscripten](http://emscripten.org/) for running the 90 | interpreter in a web browser. The `chip8.html` is a wrapper around the 91 | Emscripten-generated JavaScript. If you want to use this implementation: 92 | 93 | 1. You need to put your CHIP-8 binary file in a `./GAMES/` directory 94 | 2. Run `make -f emscripten.mak` 95 | 3. Change the `Module.arguments` variable in the JavaScript in `chip8.html` 96 | 4. Serve `chip8.html` in a web server. 97 | 98 | I built the emscripten version through the emscripten SDK installed 99 | according to the [installation instructions][emscripten-install]. I had 100 | some linker errors with Ubuntu's `emscripten` package that I couldn't 101 | resolve. 102 | 103 | [emscripten-install]: http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#sdk-download-and-install 104 | 105 | ### Win32/GDI Implementation 106 | 107 | The native Windows version uses a simple hook around the Win32 GDI and requires 108 | no third party dependencies. 109 | 110 | `gdi.h` and `gdi.c` implements the native Windows code. It implements a 111 | `WinMain` function with the main Win32 events processing loop. It binds the 112 | window's GDI context to a `Bitmap` object so that a render function can draw 113 | onto it and fires off periodic `WM_PAINT` messages which calls the `render()` 114 | function to draw the screen. 115 | 116 | ## Implementation Notes 117 | 118 | I've consulted several sources for my implementation (see references below), 119 | and there were some discrepancies. This is how I handled them: 120 | 121 | * Regarding `2nnn`, [cowgod][] says the stack pointer is incremented first (i.e. 122 | `stack[++SP]`), but that skips `stack[0]`. My implementation does it the 123 | other way round. 124 | * ~~The `Fx55` and `Fx65` instructions doesn't change `I` in my implementation:~~ 125 | * This is a known [quirk][langhoff]. 126 | * The interpreter now provides `QUIRKS_MEM_CHIP8` to control this 127 | * I've read [David Winter's emulator][winter]'s documentation when I started, but I 128 | implemented things differently: 129 | * His emulator scrolls only 2 pixels if it is in low-res mode, but 4 pixels 130 | is consistent with [Octo][]. 131 | * His emulator's `Dxy0` instruction apparently also works differently in 132 | lo-res mode. 133 | * ~~[instruction-draw][] says that images aren't generally wrapped, but 134 | [muller][] and [Octo][] seems to think differently.~~ 135 | * This is alsp known [quirk][langhoff]. 136 | * The interpreter now provides `QUIRKS_CLIPPING` to control this 137 | * According to [chip8-wiki][], the upper 256 bytes of RAM is used for the display, but it 138 | seems that modern interpreters don't do that. Besides, you'd need 1024 bytes 139 | to store the SCHIP's hi-res mode. 140 | * `hp48_flags` is not cleared between runs (See [octo-superchip]); I don't make any effort 141 | to persist them, though. 142 | * Apparently there are CHIP-8 interpreters out there that don't use the 143 | standard 64x32 and 128x64 resolutions, but I don't support those. 144 | * As far as I can tell, there is not much in terms of standard timings on 145 | CHIP-8 implementations. My implementation allows you to specify the speed as 146 | the number of instructions to execute per second (through the global variable 147 | `speed` in `render.c`). The value of 1200 instructions per second seems like 148 | a good value to start with. 149 | 150 | ## References and Links 151 | 152 | * [Wikipedia entry][wikipedia] 153 | * [Cowgod's Chip-8 Technical Reference v1.0][cowgod], by Thomas P. Greene, 154 | * [How to write an emulator (CHIP-8 interpreter)][muller] by Laurence Muller (archived) 155 | * [CHIP8 A CHIP8/SCHIP emulator Version 2.2.0][winter], by David Winter 156 | * [Chip 8 instruction set][chip8def], author unknown(?) 157 | * [Byte Magazine Volume 03 Number 12 - Life pp. 108-122. "An Easy 158 | Programming System,"][byte] by Joseph Weisbecker 159 | * [chip8.wikia.com][chip8-wiki] 160 | * Their page on the [Draw instruction][instruction-draw] 161 | * [Mastering CHIP-8][mikolay] by Matthew Mikolay 162 | * [Octo][], John Earnest 163 | * The [Octo SuperChip document][octo-superchip], by John Earnest 164 | * [codeslinger.co.uk](http://www.codeslinger.co.uk/pages/projects/chip8/primitive.html) 165 | * [CHIP‐8 Technical Reference](https://github.com/mattmikolay/chip-8/wiki/CHIP%E2%80%908-Technical-Reference), by Matthew Mikolay 166 | * [corax89' chip8-test-rom](https://github.com/corax89/chip8-test-rom) 167 | * [Timendus' chip8-test-suite][Timendus] was extremely useful to help clarify and fix the quirks. 168 | * Timendus' [Silicon8](https://github.com/Timendus/silicon8/) CHIP8 implementation 169 | * Tobias V. Langhoff's [Guide to making a CHIP-8 emulator][langhoff] 170 | * This one is very useful for explaining the various quirks 171 | * [Chip-8 on the COSMAC VIP: Drawing Sprites](https://web.archive.org/web/20200925222127if_/https://laurencescotford.com/chip-8-on-the-cosmac-vip-drawing-sprites/), by Laurence Scotford (archive link) 172 | * [CHIP-8 extensions and compatibility](https://chip-8.github.io/extensions/) - 173 | explains several of the variants out there 174 | * 175 | * The [load_quirk and shift_quirk](https://github.com/zaymat/super-chip8#load_quirk-and-shift_quirk) 176 | section of that README has another explaination of some of the 177 | quirks, along with a list of known games that need them. 178 | 179 | * has a collection of ROMs I used for testing 180 | * - Archive of CHIP8 programs. 181 | * 182 | 183 | [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8 184 | [cowgod]: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM 185 | [muller]: https://web.archive.org/web/20110426134039if_/http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/ 186 | [winter]: http://devernay.free.fr/hacks/chip8/CHIP8.DOC 187 | [chip8def]: http://devernay.free.fr/hacks/chip8/chip8def.htm 188 | [byte]: https://archive.org/details/byte-magazine-1978-12 189 | [chip8-wiki]: 190 | [instruction-draw]: http://chip8.wikia.com/wiki/Instruction_Draw 191 | [mikolay]: 192 | [octo]: https://github.com/JohnEarnest/Octo 193 | [octo-superchip]: https://github.com/JohnEarnest/Octo/blob/gh-pages/docs/SuperChip.md 194 | [langhoff]: https://tobiasvl.github.io/blog/write-a-chip-8-emulator/ 195 | [Timendus]: https://github.com/Timendus/chip8-test-suite 196 | 197 | ## License 198 | 199 | This code is licensed under the [Apache license version 2](http://www.apache.org/licenses/LICENSE-2.0): 200 | 201 | ``` 202 | Copyright 2015-2016 Werner Stoop 203 | 204 | Licensed under the Apache License, Version 2.0 (the "License"); 205 | you may not use this file except in compliance with the License. 206 | You may obtain a copy of the License at 207 | 208 | http://www.apache.org/licenses/LICENSE-2.0 209 | 210 | Unless required by applicable law or agreed to in writing, software 211 | distributed under the License is distributed on an "AS IS" BASIS, 212 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 213 | See the License for the specific language governing permissions and 214 | limitations under the License. 215 | ``` 216 | 217 | ## TODO/Roadmap/Ideas 218 | 219 | * [ ] I really need to fix the "Display wait" quirk. See [Timendus][]'s `5-quirks.ch8` test. 220 | * [x] The quirks need to be in a flags variable so that they can be controlled at runtime 221 | * [x] The runtime should have a `-q` command line option to control the quirks 222 | * [x] The assembler needs an `include "file.asm"` directive. 223 | * [x] You need a way to specify how to do the include, because the assembler must be 224 | usable even if you're not loading the source from files. I suggest a function pointer 225 | that points to `c8_load_txt()` by default, but can be made to point elsewhere (or set to 226 | `NULL` and disable includes completely) 227 | * [x] I should consider a `text "hello"` directive in the assembler, that places a null 228 | terminated string in the bytecode. Users might be able to display the text at some point 229 | if you have the right sprites; [Octo][] does it. 230 | * [x] Allow for some hooks in the library to let the `SYS nnn` (`0nnn`) instructions break 231 | out into the environment outside. 232 | * It's meant as a bit of a joke, might be neat if you embed a CHIP-8 interpreter 233 | in another program and call out to it as a sort of scripting language. 234 | * [x] Command line option, like `-m addr=val`, that will set the byte at `addr` to `val` in the 235 | RAM before running the interpreter. 236 | * A immediate use case is for, example, [Timendus][]'s `5-quirks.ch8` test that allows you 237 | to write a value between 1 and 3 to `0x1FF` and then the program will bypass the initial 238 | menu and skip directly to the corresponding test. I imagine that while developing and 239 | debugging CHIP-8 programs it might be useful to have such a mechanism. 240 | * [x] Fix the assembler that doesn't do any bounds checks on `stepper->token` 241 | * [ ] Breakpoints in the debugger 242 | * [ ] ~~A `.map` file output by the assembler...~~ 243 | 244 | Porting to the Amiga 500 might be an interesting challenge to get it truly portable: 245 | The Amiga's bus is word aligned, so if the program counter is ever an odd number then 246 | the system might crash when it tries to retrieve an instruction. Also, the Amiga is big 247 | endian, so that might reveal some problems as well. 248 | 249 | [XO-Chip compatibility](http://johnearnest.github.io/Octo/docs/XO-ChipSpecification.html) seems 250 | like something worth striving for. [Here](https://chip-8.github.io/extensions/#xo-chip)'s a 251 | short checklist of the changes. Also look at how [Octo](https://chip-8.github.io/extensions/#octo) 252 | modifies some instructions. 253 | -------------------------------------------------------------------------------- /chip8.h: -------------------------------------------------------------------------------- 1 | /** chip8.h 2 | * ======= 3 | * 4 | * ![toc] 5 | * ## Introduction 6 | * 7 | * This header provides the public API for the core of the CHIP-8 toolkit. 8 | * 9 | * The core of the CHIP-8 interpreter is written to be platform independent. 10 | * To use this core, one needs to use this API to create 11 | * a platform-specific _implementation_. This _implementation_ should do the 12 | * following: 13 | * 14 | * * Step through the instructions in the interpreter; see `c8_step()`. 15 | * * Draw the graphics; The resolution is obtained through `c8_resolution()` 16 | * and then the individual pixel states are read through `c8_get_pixel()` 17 | * * Tell the interpreter about the state of the keyboard; It should call 18 | * `c8_key_down()` and `c8_key_up()` when the state of the keyboard 19 | * changes. 20 | * * Tell the intepreter about every 60Hz timer tick; see `c8_60hz_tick()`. 21 | * * Play sound. Since the sound is just a buzzer, you may wish to skip this; 22 | * See `c8_sound()`. 23 | * 24 | * The header also includes an API for the CHIP-8 assembler and a disassembler. 25 | * The syntax is based on **Cowgod's Chip-8 Technical Reference v1.0** 26 | * by Thomas P. Greene available at 27 | * 28 | * 29 | * ## License 30 | * This code is licensed under the [Apache license version 2](http://www.apache.org/licenses/LICENSE-2.0): 31 | * 32 | * ``` 33 | * Copyright 2015-2016 Werner Stoop 34 | * 35 | * Licensed under the Apache License, Version 2.0 (the "License"); 36 | * you may not use this file except in compliance with the License. 37 | * You may obtain a copy of the License at 38 | * 39 | * http://www.apache.org/licenses/LICENSE-2.0 40 | * 41 | * Unless required by applicable law or agreed to in writing, software 42 | * distributed under the License is distributed on an "AS IS" BASIS, 43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | * See the License for the specific language governing permissions and 45 | * limitations under the License. 46 | * ``` 47 | */ 48 | 49 | /** ## Definitions */ 50 | /** `#define TOTAL_RAM 4096` \ 51 | * Maximum addressable memory in the interpreter. 52 | */ 53 | #define TOTAL_RAM 4096 54 | 55 | /** `#define PROG_OFFSET 512` \ 56 | * Offset of the program in RAM. Should be 512, but 57 | * apparently there are some computers where this is `0x600` (See [wikipedia][]). 58 | */ 59 | #define PROG_OFFSET 512 60 | 61 | /** `#define MAX_MESSAGE_TEXT 128` \ 62 | * Size of the buffer used internally by `c8_message()` 63 | */ 64 | #define MAX_MESSAGE_TEXT 128 65 | 66 | /** `typedef struct chip8_t` 67 | * 68 | * Structure to keep the interpreter's state and registers. 69 | * 70 | * It has these members: 71 | * 72 | * * `uint8_t V[16]` - CHIP-8 registers 73 | * * `uint8_t RAM[TOTAL_RAM]` - Interpreter RAM 74 | * * `uint16_t PC` - Program counter 75 | * * `uint16_t I` - Index register 76 | * * `uint8_t DT, ST` - Delay timer and sound timer 77 | * * `uint16_t stack[16]` - stack 78 | * * `uint8_t SP` - Stack pointer 79 | * 80 | */ 81 | typedef struct { 82 | uint8_t V[16]; 83 | uint8_t RAM[TOTAL_RAM]; 84 | uint16_t PC; 85 | uint16_t I; 86 | uint8_t DT, ST; 87 | uint16_t stack[16]; 88 | uint8_t SP; 89 | } chip8_t; 90 | 91 | extern chip8_t C8; 92 | 93 | /** 94 | * ## Quirks 95 | * 96 | * The definitions below determine how the Quirks, as defined in Timendus' [quirks-test][] 97 | * are handled. 98 | * 99 | * Detailed explainations of the issues can be found in [langhoff][]. 100 | * 101 | * Use the function `c8_set_quirks()` to a combination of the following flags bitwise-OR'ed together: 102 | * 103 | * * `QUIRKS_VF_RESET` - Controls whether the AND/OR/XOR instructions reset the VF register to 0. 104 | * * The original COSMAC CHIP8 performed these directly in the ALU, so VF was 105 | * always affected, but later implementations didn't. 106 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#logical-and-arithmetic-instructions). 107 | * * `QUIRKS_MEM_CHIP8` - Controls whether the `I` register is incremented by the Fx55 and Fx65 instructions. 108 | * * The original COSMAC CHIP8 incremented the `I` register, but modern implementations don't. 109 | * * Don't use it if you're unsure, because that would be more compatible. 110 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#fx55-and-fx65-store-and-load-memory). 111 | * * `QUIRKS_SHIFT` - If it is not set, `Vy` is copied into `Vx` when performing the `8xy6` and `8xyE` shift instructions. 112 | * * The original CHIP8 behaved this way, but later implementations didn't. 113 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#8xy6-and-8xye-shift). 114 | * * (Note to self: The INVADERS.ch8 game I found somewhere is an example of where the shifting 115 | * needs to be on) 116 | * * `QUIRKS_JUMP` - The CHIP-48 and SUPER-CHIP implementations originally implemented the 117 | * instruction as Bxnn as a jump to `Vx + xnn` rather than as `V0 + xnn` 118 | * * (it may have been a mistake). 119 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#bnnn-jump-with-offset) 120 | * * `QUIRKS_CLIPPING` - With clipping enabled, sprites off the edge of the screen are clipped. 121 | * If it is disabled, then those sprites are wrapped to the other side. 122 | * * This is associated with XO-CHIP 123 | * 124 | * In addition, these constants are defined for convenience: 125 | * 126 | * * `QUIRKS_DEFAULT` - The default setting: It enables `VF Reset`, `Shift` and `Clipping`. 127 | * * `QUIRKS_CHIP8` - Enables `VF Reset`, `Mem CHIP8`, `Disp Wait` and `Clipping` for the best compatibility with 128 | * the original CHIP8. 129 | * * `QUIRKS_SCHIP` - Enables `Clipping`, `Shift` and `Jump` for the best compatibility with SUPER-CHIP. 130 | * 131 | * [langhoff]: https://tobiasvl.github.io/blog/write-a-chip-8-emulator/ 132 | * [quirks-test]: https://github.com/Timendus/chip8-test-suite/tree/main#quirks-test 133 | */ 134 | #define QUIRKS_VF_RESET 0x01 135 | #define QUIRKS_MEM_CHIP8 0x02 136 | #define QUIRKS_DISP_WAIT 0x04 137 | #define QUIRKS_CLIPPING 0x08 138 | #define QUIRKS_SHIFT 0x10 139 | #define QUIRKS_JUMP 0x20 140 | 141 | #define QUIRKS_DEFAULT (QUIRKS_VF_RESET | QUIRKS_SHIFT | QUIRKS_CLIPPING) 142 | #define QUIRKS_CHIP8 (QUIRKS_VF_RESET | QUIRKS_MEM_CHIP8 | QUIRKS_DISP_WAIT | QUIRKS_CLIPPING) 143 | #define QUIRKS_SCHIP (QUIRKS_CLIPPING | QUIRKS_SHIFT | QUIRKS_JUMP) 144 | 145 | /** 146 | * `void c8_set_quirks(unsigned int q);` \ 147 | */ 148 | void c8_set_quirks(unsigned int q); 149 | 150 | /** 151 | * `unsigned int c8_get_quirks();` \ 152 | */ 153 | unsigned int c8_get_quirks(); 154 | 155 | /** 156 | * ## Utilities 157 | * 158 | * `extern int c8_verbose;` \ 159 | * Set to non-zero to turn on verbose mode. 160 | * 161 | * The higher the value, the more verbose the output. 162 | */ 163 | extern int c8_verbose; 164 | 165 | /** ## Interpreter */ 166 | 167 | /** `void c8_reset();` \ 168 | * Resets the state of the interpreter so that a new program 169 | * can be executed. 170 | */ 171 | void c8_reset(); 172 | 173 | /** `void c8_step();` \ 174 | * Steps through a single instruction in the interpreter. 175 | * 176 | * This function forms the core of the interpreter. 177 | */ 178 | void c8_step(); 179 | 180 | /** `int c8_ended();` \ 181 | * Returns true if the interpreter has ended. 182 | * 183 | * The interpreter has ended if a **00FD** instruction has been encountered. 184 | * 185 | * The **00FD** instruction is actually SuperChip specific. 186 | */ 187 | int c8_ended(); 188 | 189 | /** `int c8_waitkey();` \ 190 | * Returns true if the interpreter is waiting for keyboard input. 191 | * 192 | * The **Fx0A** instruction is the one that waits for a specific key to be pressed. 193 | */ 194 | int c8_waitkey(); 195 | 196 | /** 197 | * `typedef int (*c8_sys_hook_t)(unsigned int nnn);` \ 198 | * `extern c8_sys_hook_t c8_sys_hook;` \ 199 | * 200 | * If `c8_sys_hook` is not null, then the interpreter will call it when 201 | * it encounters a `SYS nnn` (`0nnn`) instruction, with `nnn` as a parameter. 202 | * 203 | * The function should return a non-zero value on success. If it returns 204 | * zero, the interpreter will halt. 205 | */ 206 | typedef int (*c8_sys_hook_t)(unsigned int nnn); 207 | 208 | extern c8_sys_hook_t c8_sys_hook; 209 | 210 | /** ## Debugging */ 211 | 212 | /** `uint8_t c8_get(uint16_t addr);` \ 213 | * Gets the value of a byte at a specific address `addr` in 214 | * the interpreter's RAM. 215 | */ 216 | uint8_t c8_get(uint16_t addr); 217 | 218 | /** `void c8_set(uint16_t addr, uint8_t byte);` \ 219 | * Sets the value of the `byte` at a specific address `addr` in 220 | * the interpreter's RAM. 221 | */ 222 | void c8_set(uint16_t addr, uint8_t byte); 223 | 224 | /** `uint16_t c8_opcode(uint16_t addr);` \ 225 | * Gets the opcode at a specific address `addr` in the interpreter's RAM. 226 | */ 227 | uint16_t c8_opcode(uint16_t addr); 228 | 229 | /** `uint16_t c8_get_pc();` \ 230 | * Gets the current address pointed to by the interpreter's program 231 | * counter (PC). 232 | * 233 | */ 234 | uint16_t c8_get_pc(); 235 | 236 | /** `uint16_t c8_prog_size();` \ 237 | * Gets the size of the program in the interpreter's RAM. 238 | * 239 | * It basically just search for the last non-zero byte in RAM. 240 | */ 241 | uint16_t c8_prog_size(); 242 | 243 | /** `uint8_t c8_get_reg(uint8_t r);` \ 244 | * Gets the value of the register `Vr` where `0` <= `r` <= `F`. 245 | */ 246 | uint8_t c8_get_reg(uint8_t r); 247 | 248 | /** `int (*c8_rand)();` \ 249 | * Points to the function that should be used to generate 250 | * random numbers for the **Cxkk** instruction. 251 | * 252 | * The default value points to `rand()` in the standard library. 253 | * This implies that `srand()` should be called at the 254 | * start of the program. 255 | */ 256 | extern int (*c8_rand)(); 257 | 258 | /** ## Graphics 259 | * The _implementation_ should provide a platform specific way for the interpreter 260 | * core to draw its graphics. 261 | * 262 | */ 263 | 264 | /** `int c8_screen_updated();` \ 265 | * Returns true if the last instruction executed by `c8_step()` changed the graphics, 266 | * in which case the display should be updated. 267 | */ 268 | int c8_screen_updated(); 269 | 270 | /** `int c8_resolution(int *w, int *h);` \ 271 | * Loads the current resolution of the interpreter into `w` and `h`. 272 | * 273 | * The interpreter will be in either the normal CHIP-8 64x32 resolution or 274 | * in the SuperChip-specific 128x64 "high" resolution mode. 275 | * 276 | * It returns 1 if the interpreter is in high resolution mode, 0 otherwise. 277 | */ 278 | int c8_resolution(int *w, int *h); 279 | 280 | /** `int c8_get_pixel(int x, int y);` \ 281 | * Gets the status of the pixel at (x,y). 282 | * 283 | * Returns 1 if the pixel is set - i.e. it should be drawn in the foreground colour. 284 | * 285 | * Returns 0 if the pixel is cleared - i.e. it should be drawn in the background colour. 286 | */ 287 | int c8_get_pixel(int x, int y); 288 | 289 | /** ## Keyboard routines 290 | * The _implementation_ should use these functions to tell the interpreter 291 | * about changes in the keyboard state: 292 | */ 293 | 294 | /** `void c8_key_down(uint8_t k);` \ 295 | * Sets the state of key `k` to pressed. 296 | */ 297 | void c8_key_down(uint8_t k); 298 | 299 | /** `void c8_key_up(uint8_t k);` \ 300 | * Sets the state of key `k` to released. 301 | */ 302 | void c8_key_up(uint8_t k); 303 | 304 | /** ## Timer and sound functions 305 | * CHIP-8 has a 60Hz timer that updates a delay timer and a sound timer 306 | * register. The _implementation_ needs to tell the interpreter about these 307 | * 60Hz ticks. 308 | */ 309 | 310 | /** `void c8_60hz_tick();` \ 311 | * Executes a CHIP-8 60Hz timer tick. 312 | * 313 | * This decrements the delay and sound timers if they are non-zero. 314 | * 315 | * The _implementation_ should call this function 60 times per second. 316 | */ 317 | void c8_60hz_tick(); 318 | 319 | /** `int c8_sound();` \ 320 | * Returns true if the sound timer is non-zero and sound should be played. 321 | * 322 | * CHIP-8 sounds use a single tone over which programs have no control. 323 | */ 324 | int c8_sound(); 325 | 326 | /** ## I/O Routines 327 | * The toolkit provides several functions to save 328 | * and load CHIP-8 programs to and from disk. 329 | */ 330 | 331 | /** `size_t c8_load_program(uint8_t program[], size_t n);` \ 332 | * Loads a program's bytes (of length `n`) into the interpreter's RAM. 333 | * It returns the number of bytes loaded. 334 | */ 335 | size_t c8_load_program(uint8_t program[], size_t n); 336 | 337 | /** `int c8_load_file(const char *fname);` \ 338 | * Loads a CHIP-8 file from disk into the interpreter's RAM. 339 | * 340 | * Returns the number of bytes read, 0 on error. 341 | */ 342 | int c8_load_file(const char *fname); 343 | 344 | /** `int c8_save_file(const char *fname);` \ 345 | * Writes the contents of the interpreter's RAM to a file. 346 | * 347 | * This is typically done after `c8_assemble()` to write the 348 | * final program to disk. 349 | * 350 | * Returns the number of bytes written on success, 0 on failure. 351 | */ 352 | int c8_save_file(const char *fname); 353 | 354 | /** `char *c8_load_txt(const char *fname);` \ 355 | * Utility function that loads a text file. 356 | * 357 | * It `malloc()`s a buffer large enough to hold the entire file 358 | * that needs to be `free()`ed afterwards. 359 | * 360 | * Returns the buffer, or `NULL` on error. 361 | */ 362 | char *c8_load_txt(const char *fname); 363 | 364 | /** ## Output and Error handling */ 365 | 366 | /** `extern int (*c8_puts)(const char* s);` \ 367 | * Pointer to a function that outputs text messages. 368 | * 369 | * The default implementation wraps around `fputs()` 370 | * and writes to `stdout`; change it if output needs 371 | * to be done differently. 372 | */ 373 | extern int (*c8_puts)(const char* s); 374 | 375 | /** `int c8_message(const char *msg, ...);` \ 376 | * Outputs a formatted message. 377 | * 378 | * It has the same symantics as `printf()` and 379 | * calls `c8_puts()` to output a message. 380 | * 381 | * Returns the value of the `c8_puts()` call. 382 | */ 383 | int c8_message(const char *msg, ...); 384 | 385 | /** `extern char c8_message_text[];` \ 386 | * The internal buffer used by `c8_message()`. 387 | */ 388 | extern char c8_message_text[]; 389 | 390 | /** 391 | * ## Assembler 392 | * 393 | */ 394 | 395 | /** `int c8_assemble(const char *text);` \ 396 | * Assembles a block of text into the interpreter's RAM. 397 | * 398 | * The assembled program can be written to a file using `c8_save_file()`. 399 | * 400 | * `c8_load_txt()` is provided as a utility function to load 401 | * a text file that can be assembled. 402 | * 403 | * See `asmmain.c` for an example of a program that uses this function. 404 | */ 405 | int c8_assemble(const char *text); 406 | 407 | /** 408 | * `typedef char *(*c8_include_callback_t)(const char *fname);` \ 409 | * `extern c8_include_callback_t c8_include_callback;` \ 410 | * 411 | * Controls how `include` directives are handled by the assembler. 412 | * 413 | * The callback should return the text in the specified file. The 414 | * return value should be allocated on the heap, because the assembler 415 | * will call `free()` on it when it is done. 416 | * 417 | * `c8_include_callback` defaults to `c8_load_txt()`. 418 | */ 419 | typedef char *(*c8_include_callback_t)(const char *fname); 420 | extern c8_include_callback_t c8_include_callback; 421 | 422 | /** 423 | * ## Disassembler 424 | * 425 | */ 426 | 427 | /** `void c8_disasm_start();` \ 428 | * Initializes the variables used by the disassembler to track its state. 429 | */ 430 | void c8_disasm_start(); 431 | 432 | /** `void c8_disasm_reachable(uint16_t addr)` \ 433 | * 434 | * Marks an address in the program as reachable through a jump instruction. 435 | * 436 | * The disassembler follows the different branches in a program to determine 437 | * which instructions are reachable. It does this to determine which bytes in 438 | * the program are code and which are data. Unfortunately this does not work for 439 | * the `JP V0, nnn` (`Bnnn`) instruction, because it won't know what the value 440 | * in `V0` is (and `Bnnn`'s behaviour also depends on the `QUIRKS_JUMP` quirk). 441 | * 442 | * If you can determine the `Bnnn`'s intended destination, you could mark 443 | * that destination as reachable, and rerun the disassembler. 444 | */ 445 | void c8_disasm_reachable(uint16_t addr); 446 | 447 | /** `void c8_disasm();` \ 448 | * Disassembles the program currently in the interpreter's RAM. 449 | * 450 | * The output is written through `c8_puts()`. 451 | * 452 | * See `dasmmain.c` for an example of a program that uses this function. 453 | */ 454 | void c8_disasm(); 455 | 456 | /** 457 | * [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8 458 | * 459 | */ 460 | -------------------------------------------------------------------------------- /render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(SDL2) || defined(SDL) || defined(__EMSCRIPTEN__) 12 | # include "sdl/pocadv.h" 13 | #else 14 | # include 15 | # include "gdi/gdi.h" 16 | #endif 17 | 18 | #ifndef CRT_BLUR 19 | # define CRT_BLUR 0 20 | #endif 21 | #ifndef CRT_NOISE 22 | # define CRT_NOISE 0 23 | #endif 24 | 25 | #include "chip8.h" 26 | #include "bmp.h" 27 | 28 | /* number of instructions to execute per second */ 29 | static int speed = 1200; 30 | 31 | /* Foreground color */ 32 | static int fg_color = 0xAAAAFF; 33 | 34 | /* Background color */ 35 | static int bg_color = 0x000055; 36 | 37 | /* Is the interpreter running? Set to 0 to enter "debug" mode */ 38 | static int running = 1; 39 | 40 | static Bitmap *chip8_screen; 41 | static Bitmap *hud; 42 | 43 | /* These are the same keybindings [Octo][]'s */ 44 | static unsigned int Key_Mapping[16] = { 45 | #if defined(SDL) || defined(SDL2) 46 | KCODEA(x,X), 47 | KCODE(1), 48 | KCODE(2), 49 | KCODE(3), 50 | KCODEA(q,Q), 51 | KCODEA(w,W), 52 | KCODEA(e,E), 53 | KCODEA(a,A), 54 | KCODEA(s,S), 55 | KCODEA(d,D), 56 | KCODEA(z,Z), 57 | KCODEA(c,C), 58 | KCODE(4), 59 | KCODEA(r,R), 60 | KCODEA(f,F), 61 | KCODEA(v,V) 62 | #else 63 | 0x58, /* '0' -> 'x' */ 64 | 0x31, /* '1' -> '1' */ 65 | 0x32, /* '2' -> '2' */ 66 | 0x33, /* '3' -> '3' */ 67 | 0x51, /* '4' -> 'q' */ 68 | 0x57, /* '5' -> 'w' */ 69 | 0x45, /* '6' -> 'e' */ 70 | 0x41, /* '7' -> 'a' */ 71 | 0x53, /* '8' -> 's' */ 72 | 0x44, /* '9' -> 'd' */ 73 | 0x5A, /* 'A' -> 'z' */ 74 | 0x43, /* 'B' -> 'c' */ 75 | 0x34, /* 'C' -> '4' */ 76 | 0x52, /* 'D' -> 'r' */ 77 | 0x46, /* 'E' -> 'f' */ 78 | 0x56, /* 'F' -> 'v' */ 79 | #endif 80 | }; 81 | 82 | static void draw_screen(); 83 | 84 | static void usage() { 85 | exit_error("Use these command line variables:\n" 86 | " -f fg : Foreground color\n" 87 | " -b bg : Background color\n" 88 | " -s spd : Specify the speed\n" 89 | " -d : Debug mode\n" 90 | " -v : increase verbosity\n" 91 | " -q quirks : sets the quirks mode\n" 92 | " `quirks` can be a comma separated combination\n" 93 | " of `none`, `vf`, `mem`, `disp`, `clip`, `shift`,\n" 94 | " `jump`, `default`, `chip8` and `schip`\n" 95 | " -a addr=val : Sets the byte in RAM at `addr` to\n" 96 | " the value `val` before executing.\n" 97 | " -h : Displays this help\n" 98 | ); 99 | } 100 | 101 | #ifdef __EMSCRIPTEN__ 102 | static int em_ready; 103 | void loaded_callback_func(const char *infile) { 104 | rlog("Loading %s...", infile); 105 | if(!c8_load_file(infile)) { 106 | exit_error("Unable to load '%s': %s\n", infile, strerror(errno)); 107 | return; 108 | } 109 | em_ready = 1; 110 | } 111 | void error_callback_func(const char *s) { 112 | rerror("Error loading %s", s); 113 | } 114 | #endif 115 | 116 | /* 117 | * This is an example of how you can write a CHIP8 program that can access the 118 | * world outside by invoking the `SYS nnn` (`0nnn`) instruction 119 | * 120 | * ``` 121 | * CLS 122 | * LD V0, #AB 123 | * SYS 2 124 | * LD V0, #CD 125 | * SYS 2 126 | * LD V0, #EF 127 | * SYS 2 128 | * LD I, string 129 | * SYS 1 130 | * EXIT 131 | * string: text "Hello World!" 132 | * ``` 133 | */ 134 | int example_sys_hook(unsigned int nnn) { 135 | switch(nnn) { 136 | case 1: { 137 | char *str = (char*)&C8.RAM[C8.I]; 138 | rlog("console: %s", str); 139 | } break; 140 | case 2: { 141 | rlog("console: V0: %02X", C8.V[0]); 142 | } break; 143 | case 3: { 144 | rlog("console: Halting interpreter; VF: %02X", C8.V[0xF]); 145 | return 0; 146 | } break; 147 | default: break; 148 | } 149 | return 1; 150 | } 151 | 152 | void init_game(int argc, char *argv[]) { 153 | 154 | const char *infile = NULL; 155 | 156 | rlog("Initializing..."); 157 | 158 | srand(time(NULL)); 159 | 160 | c8_reset(); 161 | 162 | fg_color = bm_byte_order(fg_color); 163 | bg_color = bm_byte_order(bg_color); 164 | 165 | int opt; 166 | while((opt = getopt(argc, argv, "f:b:s:dvhq:m:")) != -1) { 167 | switch(opt) { 168 | case 'v': c8_verbose++; break; 169 | case 'f': fg_color = bm_atoi(optarg); break; 170 | case 'b': bg_color = bm_atoi(optarg); break; 171 | case 's': speed = atoi(optarg); if(speed < 1) speed = 10; break; 172 | case 'd': running = 0; break; 173 | case 'q': { 174 | unsigned int quirks = 0; 175 | char *token = strtok(optarg, ","); 176 | while (token) { 177 | if(!strcmp(token, "none")) quirks = 0; 178 | else if(!strcmp(token, "vf")) quirks |= QUIRKS_VF_RESET; 179 | else if(!strcmp(token, "mem")) quirks |= QUIRKS_MEM_CHIP8; 180 | else if(!strcmp(token, "disp")) quirks |= QUIRKS_DISP_WAIT; 181 | else if(!strcmp(token, "clip")) quirks |= QUIRKS_CLIPPING; 182 | else if(!strcmp(token, "shift")) quirks |= QUIRKS_SHIFT; 183 | else if(!strcmp(token, "jump")) quirks |= QUIRKS_JUMP; 184 | else if(!strcmp(token, "default")) quirks |= QUIRKS_DEFAULT; 185 | else if(!strcmp(token, "chip8")) quirks |= QUIRKS_CHIP8; 186 | else if(!strcmp(token, "schip")) quirks |= QUIRKS_SCHIP; 187 | else rerror("warning: unknown quirk '%s'", token); 188 | token = strtok(NULL, ","); 189 | } 190 | c8_set_quirks(quirks); 191 | } break; 192 | case 'm': { 193 | int addr, val; 194 | char *token = strtok(optarg, ","); 195 | while (token) { 196 | char *delim = strchr(token, '='); 197 | if(!delim) { 198 | exit_error("error: bad field for -m; expected `addr=value`, got `%s`", token); 199 | } 200 | 201 | *delim = '\0'; 202 | delim++; 203 | addr = strtol(token, NULL, 0); 204 | val = strtol(delim, NULL, 0); 205 | if(addr < 0 || addr >= TOTAL_RAM) 206 | exit_error("error: bad address for -m: 0 <= addr < %d", TOTAL_RAM); 207 | if(val < 0 || val >= 256) 208 | exit_error("error: bad address for -m: 0 <= val < 256"); 209 | 210 | c8_set(addr, val); 211 | 212 | token = strtok(NULL, ","); 213 | } 214 | } break; 215 | case 'h': usage(); break; 216 | } 217 | } 218 | 219 | if(optind >= argc) { 220 | exit_error("You need to specify a CHIP-8 file.\n"); 221 | } 222 | infile = argv[optind++]; 223 | 224 | c8_sys_hook = example_sys_hook; 225 | 226 | #ifdef __EMSCRIPTEN__ 227 | em_ready = 0; 228 | rlog("emscripten_wget retrieving %s", infile); 229 | //emscripten_wget(infile, infile); 230 | emscripten_async_wget(infile, infile, loaded_callback_func, error_callback_func); 231 | #else 232 | rlog("Loading %s...", infile); 233 | if(!c8_load_file(infile)) { 234 | exit_error("Unable to load '%s': %s\n", infile, strerror(errno)); 235 | } 236 | #endif 237 | 238 | bm_set_color(screen, 0x202020); 239 | bm_clear(screen); 240 | 241 | chip8_screen = bm_create(128, 64); 242 | 243 | draw_screen(); 244 | 245 | #ifdef __EMSCRIPTEN__ 246 | /* I couldn't figure out why this is necessary on the emscripten port: */ 247 | Key_Mapping[0] = KCODEA(x,X); 248 | Key_Mapping[1] = KCODE(1); 249 | Key_Mapping[2] = KCODE(2); 250 | Key_Mapping[3] = KCODE(3); 251 | Key_Mapping[4] = KCODEA(q,Q); 252 | Key_Mapping[5] = KCODEA(w,W); 253 | Key_Mapping[6] = KCODEA(e,E); 254 | Key_Mapping[7] = KCODEA(a,A); 255 | Key_Mapping[8] = KCODEA(s,S); 256 | Key_Mapping[9] = KCODEA(d,D); 257 | Key_Mapping[10] = KCODEA(z,Z); 258 | Key_Mapping[11] = KCODEA(c,C); 259 | Key_Mapping[12] = KCODE(4); 260 | Key_Mapping[13] = KCODEA(r,R); 261 | Key_Mapping[14] = KCODEA(f,F); 262 | Key_Mapping[15] = KCODEA(v,V); 263 | #endif 264 | 265 | hud = bm_create(128, 24); 266 | if(!hud) 267 | exit_error("unable to create HUD"); 268 | 269 | rlog("Initialized."); 270 | } 271 | 272 | void deinit_game() { 273 | bm_free(hud); 274 | bm_free(chip8_screen); 275 | rlog("Done."); 276 | } 277 | 278 | #if CRT_BLUR 279 | static void add_bitmaps(Bitmap *b1, Bitmap *b2) { 280 | int x,y; 281 | assert(b1->w == b2->w && b1->h == b2->h); 282 | for(y = 0; y < b1->h; y++) { 283 | for(x = 0; x < b1->w; x++) { 284 | unsigned int c1 = bm_get(b1, x, y); 285 | unsigned int c2 = bm_get(b2, x, y); 286 | unsigned int c3 = bm_lerp(c1, c2, 0.6); 287 | bm_set(b1, x, y, c3); 288 | } 289 | } 290 | } 291 | 292 | static unsigned char oldscreen_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4]; 293 | static unsigned char plotscreen_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4]; 294 | #endif 295 | #if CRT_NOISE 296 | static long nz_seed = 0L; 297 | static long nz_rand() { 298 | nz_seed = nz_seed * 1103515245L + 12345L; 299 | return (nz_seed >> 16) & 0x7FFF; 300 | } 301 | static void nz_srand(long seed) { 302 | nz_seed = seed; 303 | } 304 | static unsigned int noise(int x, int y, unsigned int col_in) { 305 | unsigned char R, G, B; 306 | bm_get_rgb(col_in, &R, &G, &B); 307 | /* 308 | https://en.wikipedia.org/wiki/Linear_congruential_generator 309 | */ 310 | int val = (int)(nz_rand() & 0x7) - 4; 311 | if(x & 0x01) val-=4; 312 | if(y & 0x02) val-=4; 313 | 314 | int iR = R + val, iG = G + val, iB = B + val; 315 | if(iR > 0xFF) iR = 0xFF; 316 | if(iR < 0) iR = 0; 317 | if(iG > 0xFF) iG = 0xFF; 318 | if(iG < 0) iG = 0; 319 | if(iB > 0xFF) iB = 0xFF; 320 | if(iB < 0) iB = 0; 321 | return bm_rgb(iR, iG, iB); 322 | } 323 | #endif 324 | 325 | static void chip8_to_bmp(Bitmap *sbmp) { 326 | int x, y, w, h; 327 | 328 | c8_resolution(&w, &h); 329 | 330 | assert(w <= bm_width(sbmp)); 331 | assert(h <= bm_height(sbmp)); 332 | //bm_bind_static(sbmp, chip8_screen_buffer, w, h); 333 | 334 | for(y = 0; y < h; y++) { 335 | for(x = 0; x < w; x++) { 336 | unsigned int c = c8_get_pixel(x,y) ? fg_color : bg_color; 337 | bm_set(sbmp, x, y, c); 338 | } 339 | } 340 | } 341 | 342 | static void draw_screen() { 343 | int w, h; 344 | 345 | chip8_to_bmp(chip8_screen); 346 | c8_resolution(&w, &h); 347 | 348 | #if CRT_BLUR 349 | /* FIXME: This won't work anymore on the new BMP API */ 350 | Bitmap plotscreen; 351 | bm_bind_static(&plotscreen, plotscreen_buffer, SCREEN_WIDTH, SCREEN_HEIGHT); 352 | 353 | Bitmap oldscreen; 354 | bm_bind_static(&oldscreen, oldscreen_buffer, SCREEN_WIDTH, SCREEN_HEIGHT); 355 | memcpy(oldscreen.data, plotscreen.data, SCREEN_WIDTH * SCREEN_HEIGHT * 4); 356 | 357 | bm_smooth(&oldscreen); 358 | bm_smooth(&oldscreen); 359 | bm_blit_ex(screen, 0, 0, screen->w, screen->h, &chip8_screen, 0, 0, w, h, 0); 360 | add_bitmaps(screen, &oldscreen); 361 | 362 | float smooth_kernel[] = { 0.0, 0.1, 0.0, 363 | 0.1, 0.6, 0.1, 364 | 0.0, 0.1, 0.0}; 365 | bm_apply_kernel(screen, 3, smooth_kernel); 366 | #else 367 | bm_blit_ex(screen, 0, 0, bm_width(screen), bm_height(screen), chip8_screen, 0, 0, w, h, 0); 368 | #endif 369 | 370 | #if CRT_NOISE 371 | int x, y; 372 | nz_srand(1234); 373 | for(y = 0; y < screen->h; y++) { 374 | for(x = 0; x < screen->w; x++) { 375 | unsigned int c = bm_get(screen, x, y); 376 | c = noise(x, y, c); 377 | bm_set(screen, x, y, c); 378 | } 379 | } 380 | #endif 381 | 382 | #if CRT_BLUR 383 | memcpy(plotscreen.data, screen->data, SCREEN_WIDTH * SCREEN_HEIGHT * 4); 384 | #endif 385 | } 386 | 387 | void bm_blit_blend(Bitmap *dst, int dx, int dy, Bitmap *src, int sx, int sy, int w, int h); 388 | 389 | 390 | void draw_hud() { 391 | int i; 392 | 393 | // Bitmap hud; 394 | // static unsigned char hud_buffer[128 * 24 * 4]; 395 | // bm_bind_static(&hud, hud_buffer, 128, 24); 396 | 397 | uint16_t pc = c8_get_pc(); 398 | uint16_t opcode = c8_opcode(pc); 399 | bm_set_color(hud, 0x202020); 400 | bm_clear(hud); 401 | bm_set_color(hud, 0xFFFFFF); 402 | bm_printf(hud, 1, 0, "%03X %04X", pc, opcode); 403 | for(i = 0; i < 16; i++) { 404 | bm_printf(hud, (i & 0x07) * 16, (i >> 3) * 8 + 8, "%02X", c8_get_reg(i)); 405 | } 406 | 407 | bm_blit_blend(screen, 0, bm_height(screen) - 24, hud, 0, 0, bm_width(hud), bm_height(hud)); 408 | } 409 | 410 | int render(double elapsedSeconds) { 411 | int i; 412 | static double timer = 0.0; 413 | 414 | #ifdef __EMSCRIPTEN__ 415 | if(!em_ready) return 1; 416 | #endif 417 | 418 | int key_pressed = 0; 419 | for(i = 0; i < 16; i++) { 420 | int k = Key_Mapping[i]; 421 | if(keys[k]) { 422 | key_pressed = 1; 423 | c8_key_down(i); 424 | #if !defined(NDEBUG) && 0 425 | rlog("key pressed: %X 0x%02X", i, k); 426 | #endif 427 | } else 428 | c8_key_up(i); 429 | } 430 | 431 | timer += elapsedSeconds; 432 | while(timer > 1.0/60.0) { 433 | c8_60hz_tick(); 434 | timer -= 1.0/60.0; 435 | } 436 | 437 | if(running) { 438 | /* F5 breaks the program and enters debugging mode */ 439 | if(keys[KCODE(F5)]) 440 | running = 0; 441 | 442 | /* instructions per second * elapsed seconds = number of instructions to execute */ 443 | int count = speed * elapsedSeconds; 444 | for(i = 0; i < count; i++) { 445 | if(c8_ended()) 446 | return 0; 447 | else if(c8_waitkey() && !key_pressed) 448 | return 1; 449 | 450 | c8_step(); 451 | 452 | if(c8_screen_updated()) 453 | draw_screen(); 454 | } 455 | } else { 456 | /* Debugging mode: 457 | F6 steps through the program 458 | F8 resumes 459 | */ 460 | if(keys[KCODE(F8)]) { 461 | // bm_set_color(screen, 0x202020); 462 | // bm_fillrect(screen, 0, screen->h - 24, screen->w, screen->h); 463 | running = 1; 464 | return 1; 465 | } 466 | if(keys[KCODE(F6)]) { 467 | if(c8_ended()) 468 | return 0; 469 | else if(c8_waitkey() && !key_pressed) 470 | return 1; 471 | c8_step(); 472 | if(c8_screen_updated()) { 473 | draw_screen(); 474 | } 475 | keys[KCODE(F6)] = 0; 476 | } 477 | 478 | draw_screen(); 479 | draw_hud(); 480 | } 481 | 482 | return 1; 483 | } 484 | 485 | 486 | void bm_blit_blend(Bitmap *dst, int dx, int dy, Bitmap *src, int sx, int sy, int w, int h) { 487 | int x,y, i, j; 488 | 489 | BmRect destClip = bm_get_clip(dst); 490 | 491 | if(sx < 0) { 492 | int delta = -sx; 493 | sx = 0; 494 | dx += delta; 495 | w -= delta; 496 | } 497 | 498 | if(dx < destClip.x0) { 499 | int delta = destClip.x0 - dx; 500 | sx += delta; 501 | w -= delta; 502 | dx = destClip.x0; 503 | } 504 | 505 | if(sx + w > bm_width(src)) { 506 | int delta = sx + w - bm_width(src); 507 | w -= delta; 508 | } 509 | 510 | if(dx + w > destClip.x1) { 511 | int delta = dx + w - destClip.x1; 512 | w -= delta; 513 | } 514 | 515 | if(sy < 0) { 516 | int delta = -sy; 517 | sy = 0; 518 | dy += delta; 519 | h -= delta; 520 | } 521 | 522 | if(dy < destClip.y0) { 523 | int delta = destClip.y0 - dy; 524 | sy += delta; 525 | h -= delta; 526 | dy = destClip.y0; 527 | } 528 | 529 | if(sy + h > bm_height(src)) { 530 | int delta = sy + h - bm_height(src); 531 | h -= delta; 532 | } 533 | 534 | if(dy + h > destClip.y1) { 535 | int delta = dy + h - destClip.y1; 536 | h -= delta; 537 | } 538 | 539 | if(w <= 0 || h <= 0) 540 | return; 541 | if(dx >= destClip.x1 || dx + w < destClip.x0) 542 | return; 543 | if(dy >= destClip.y1 || dy + h < destClip.y0) 544 | return; 545 | if(sx >= bm_width(src) || sx + w < 0) 546 | return; 547 | if(sy >= bm_height(src) || sy + h < 0) 548 | return; 549 | 550 | if(sx + w > bm_width(src)) { 551 | int delta = sx + w - bm_width(src); 552 | w -= delta; 553 | } 554 | 555 | if(sy + h > bm_height(src)) { 556 | int delta = sy + h - bm_height(src); 557 | h -= delta; 558 | } 559 | 560 | assert(dx >= 0 && dx + w <= destClip.x1); 561 | assert(dy >= 0 && dy + h <= destClip.y1); 562 | assert(sx >= 0 && sx + w <= bm_width(src)); 563 | assert(sy >= 0 && sy + h <= bm_height(src)); 564 | 565 | j = sy; 566 | for(y = dy; y < dy + h; y++) { 567 | i = sx; 568 | for(x = dx; x < dx + w; x++) { 569 | unsigned int c1 = (bm_get(src, i, j) >> 1) & 0x7F7F7F; 570 | unsigned int c2 = (bm_get(dst, x, y) >> 1) & 0x7F7F7F; 571 | bm_set(dst, x, y, c1 + c2); 572 | i++; 573 | } 574 | j++; 575 | } 576 | } 577 | -------------------------------------------------------------------------------- /chip8.c: -------------------------------------------------------------------------------- 1 | /* 2 | Core of the CHIP-8 interpreter. 3 | This file should be kept platform independent. Everything that is 4 | platform dependent should be moved elsewhere. 5 | 6 | 7 | TODO: Apparently SuperChip 1.0 and 1.1 has a lot of caveats that 8 | I haven't addressed in my implementation 9 | https://chip-8.github.io/extensions/#super-chip-10 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "chip8.h" 21 | 22 | 23 | static unsigned int quirks = QUIRKS_DEFAULT; 24 | 25 | void c8_set_quirks(unsigned int q) { 26 | quirks = q; 27 | } 28 | 29 | unsigned int c8_get_quirks() { 30 | return quirks; 31 | } 32 | 33 | /* Where in RAM to load the font. 34 | The font should be in the first 512 bytes of RAM (see [2]), 35 | so FONT_OFFSET should be less than or equal to 0x1B0 */ 36 | #define FONT_OFFSET 0x1B0 37 | #define HFONT_OFFSET 0x110 38 | 39 | int c8_verbose = 0; 40 | 41 | chip8_t C8; 42 | 43 | /* Display memory */ 44 | static uint8_t pixels[1024]; 45 | 46 | static int yield = 0, borked = 0; 47 | 48 | static int screen_updated; /* Screen updated */ 49 | static int hi_res; /* Hi-res mode? */ 50 | 51 | /* Keypad buffer */ 52 | static uint16_t keys; 53 | 54 | /* HP48 flags for SuperChip Fx75 and Fx85 instructions */ 55 | static uint8_t hp48_flags[16]; 56 | 57 | /* Text output function */ 58 | char c8_message_text[MAX_MESSAGE_TEXT]; 59 | static int _puts_default(const char* s) { 60 | return fputs(s, stdout); 61 | } 62 | int (*c8_puts)(const char* s) = _puts_default; 63 | 64 | int (*c8_rand)() = rand; 65 | 66 | c8_sys_hook_t c8_sys_hook = NULL; 67 | 68 | /* Standard 4x5 font */ 69 | static const uint8_t font[] = { 70 | /* '0' */ 0xF0, 0x90, 0x90, 0x90, 0xF0, 71 | /* '1' */ 0x20, 0x60, 0x20, 0x20, 0x70, 72 | /* '2' */ 0xF0, 0x10, 0xF0, 0x80, 0xF0, 73 | /* '3' */ 0xF0, 0x10, 0xF0, 0x10, 0xF0, 74 | /* '4' */ 0x90, 0x90, 0xF0, 0x10, 0x10, 75 | /* '5' */ 0xF0, 0x80, 0xF0, 0x10, 0xF0, 76 | /* '6' */ 0xF0, 0x80, 0xF0, 0x90, 0xF0, 77 | /* '7' */ 0xF0, 0x10, 0x20, 0x40, 0x40, 78 | /* '8' */ 0xF0, 0x90, 0xF0, 0x90, 0xF0, 79 | /* '9' */ 0xF0, 0x90, 0xF0, 0x10, 0xF0, 80 | /* 'A' */ 0xF0, 0x90, 0xF0, 0x90, 0x90, 81 | /* 'B' */ 0xE0, 0x90, 0xE0, 0x90, 0xE0, 82 | /* 'C' */ 0xF0, 0x80, 0x80, 0x80, 0xF0, 83 | /* 'D' */ 0xE0, 0x90, 0x90, 0x90, 0xE0, 84 | /* 'E' */ 0xF0, 0x80, 0xF0, 0x80, 0xF0, 85 | /* 'F' */ 0xF0, 0x80, 0xF0, 0x80, 0x80, 86 | }; 87 | 88 | /* SuperChip hi-res 8x10 font. */ 89 | static const uint8_t hfont[] = { 90 | /* '0' */ 0x7C, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7C, 0x00, 91 | /* '1' */ 0x08, 0x18, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3C, 0x00, 92 | /* '2' */ 0x7C, 0x82, 0x02, 0x02, 0x04, 0x18, 0x20, 0x40, 0xFE, 0x00, 93 | /* '3' */ 0x7C, 0x82, 0x02, 0x02, 0x3C, 0x02, 0x02, 0x82, 0x7C, 0x00, 94 | /* '4' */ 0x84, 0x84, 0x84, 0x84, 0xFE, 0x04, 0x04, 0x04, 0x04, 0x00, 95 | /* '5' */ 0xFE, 0x80, 0x80, 0x80, 0xFC, 0x02, 0x02, 0x82, 0x7C, 0x00, 96 | /* '6' */ 0x7C, 0x82, 0x80, 0x80, 0xFC, 0x82, 0x82, 0x82, 0x7C, 0x00, 97 | /* '7' */ 0xFE, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00, 98 | /* '8' */ 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00, 99 | /* '9' */ 0x7C, 0x82, 0x82, 0x82, 0x7E, 0x02, 0x02, 0x82, 0x7C, 0x00, 100 | /* 'A' */ 0x10, 0x28, 0x44, 0x82, 0x82, 0xFE, 0x82, 0x82, 0x82, 0x00, 101 | /* 'B' */ 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x00, 102 | /* 'C' */ 0x7C, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x82, 0x7C, 0x00, 103 | /* 'D' */ 0xFC, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFC, 0x00, 104 | /* 'E' */ 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0xFE, 0x00, 105 | /* 'F' */ 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0x80, 0x00, 106 | }; 107 | 108 | void c8_reset() { 109 | memset(C8.V, 0, sizeof C8.V); 110 | memset(C8.RAM, 0, sizeof C8.RAM); 111 | C8.PC = PROG_OFFSET; 112 | C8.I = 0; 113 | C8.DT = 0; 114 | C8.ST = 0; 115 | C8.SP = 0; 116 | memset(C8.stack, 0, sizeof C8.stack); 117 | 118 | assert(FONT_OFFSET + sizeof font <= PROG_OFFSET); 119 | memcpy(C8.RAM + FONT_OFFSET, font, sizeof font); 120 | assert(HFONT_OFFSET + sizeof hfont <= FONT_OFFSET); 121 | memcpy(C8.RAM + HFONT_OFFSET, hfont, sizeof hfont); 122 | 123 | hi_res = 0; 124 | screen_updated = 0; 125 | yield = 0; 126 | borked = 0; 127 | } 128 | 129 | void c8_step() { 130 | assert(C8.PC < TOTAL_RAM); 131 | 132 | if(yield || borked) return; 133 | 134 | uint16_t opcode = C8.RAM[C8.PC] << 8 | C8.RAM[C8.PC+1]; 135 | C8.PC += 2; 136 | 137 | uint8_t x = (opcode >> 8) & 0x0F; 138 | uint8_t y = (opcode >> 4) & 0x0F; 139 | uint8_t nibble = opcode & 0x0F; 140 | uint16_t nnn = opcode & 0x0FFF; 141 | uint8_t kk = opcode & 0xFF; 142 | 143 | int row, col; 144 | 145 | screen_updated = 0; 146 | 147 | switch(opcode & 0xF000) { 148 | case 0x0000: 149 | if(opcode == 0x00E0) { 150 | /* CLS */ 151 | memset(pixels, 0, sizeof pixels); 152 | screen_updated = 1; 153 | } else if(opcode == 0x00EE) { 154 | /* RET */ 155 | if(C8.SP == 0){ 156 | /* You've got problems */ 157 | borked = 1; 158 | return; 159 | } 160 | C8.PC = C8.stack[--C8.SP]; 161 | } else if((opcode & 0xFFF0) == 0x00C0) { 162 | /* SCD nibble */ 163 | c8_resolution(&col, &row); 164 | row--; 165 | col >>= 3; 166 | while(row - nibble >= 0) { 167 | memcpy(pixels + row * col, pixels + (row - nibble) * col, col); 168 | row--; 169 | } 170 | memset(pixels, 0x0, nibble * col); 171 | screen_updated = 1; 172 | } else if(opcode == 0x00FB) { 173 | /* SCR */ 174 | c8_resolution(&col, &row); 175 | col >>= 3; 176 | for(y = 0; y < row; y++) { 177 | for(x = col - 1; x > 0; x--) { 178 | pixels[y * col + x] = (pixels[y * col + x] << 4) | (pixels[y * col + x - 1] >> 4); 179 | } 180 | pixels[y * col] <<= 4; 181 | } 182 | screen_updated = 1; 183 | } else if(opcode == 0x00FC) { 184 | /* SCL */ 185 | c8_resolution(&col, &row); 186 | col >>= 3; 187 | for(y = 0; y < row; y++) { 188 | for(x = 0; x < col - 1; x++) { 189 | pixels[y * col + x] = (pixels[y * col + x] >> 4) | (pixels[y * col + x + 1] << 4); 190 | } 191 | pixels[y * col + x] >>= 4; 192 | } 193 | screen_updated = 1; 194 | } else if(opcode == 0x00FD) { 195 | /* EXIT */ 196 | C8.PC -= 2; /* reset the PC to the 00FD */ 197 | /* subsequent calls will encounter the 00FD again, 198 | and c8_ended() will return 1 */ 199 | return; 200 | } else if(opcode == 0x00FE) { 201 | /* LOW */ 202 | if(hi_res) 203 | screen_updated = 1; 204 | hi_res = 0; 205 | } else if(opcode == 0x00FF) { 206 | /* HIGH */ 207 | if(!hi_res) 208 | screen_updated = 1; 209 | hi_res = 1; 210 | } else { 211 | /* SYS: If there's a hook, call it otherwise treat it as a no-op */ 212 | if(c8_sys_hook) { 213 | int result = c8_sys_hook(nnn); 214 | if(!result) 215 | borked = 1; 216 | } 217 | } 218 | break; 219 | case 0x1000: 220 | /* JP nnn */ 221 | C8.PC = nnn; 222 | break; 223 | case 0x2000: 224 | /* CALL nnn */ 225 | if(C8.SP >= 16) return; /* See RET */ 226 | C8.stack[C8.SP++] = C8.PC; 227 | C8.PC = nnn; 228 | break; 229 | case 0x3000: 230 | /* SE Vx, kk */ 231 | if(C8.V[x] == kk) C8.PC += 2; 232 | break; 233 | case 0x4000: 234 | /* SNE Vx, kk */ 235 | if(C8.V[x] != kk) C8.PC += 2; 236 | break; 237 | case 0x5000: 238 | /* SE Vx, Vy */ 239 | if(C8.V[x] == C8.V[y]) C8.PC += 2; 240 | break; 241 | case 0x6000: 242 | /* LD Vx, kk */ 243 | C8.V[x] = kk; 244 | break; 245 | case 0x7000: 246 | /* ADD Vx, kk */ 247 | C8.V[x] += kk; 248 | break; 249 | case 0x8000: { 250 | uint16_t ans, carry; 251 | switch(nibble) { 252 | case 0x0: 253 | /* LD Vx, Vy */ 254 | C8.V[x] = C8.V[y]; 255 | break; 256 | case 0x1: 257 | /* OR Vx, Vy */ 258 | C8.V[x] |= C8.V[y]; 259 | if(quirks & QUIRKS_VF_RESET) 260 | C8.V[0xF] = 0; 261 | break; 262 | case 0x2: 263 | /* AND Vx, Vy */ 264 | C8.V[x] &= C8.V[y]; 265 | if(quirks & QUIRKS_VF_RESET) 266 | C8.V[0xF] = 0; 267 | break; 268 | case 0x3: 269 | /* XOR Vx, Vy */ 270 | C8.V[x] ^= C8.V[y]; 271 | if(quirks & QUIRKS_VF_RESET) 272 | C8.V[0xF] = 0; 273 | break; 274 | case 0x4: 275 | /* ADD Vx, Vy */ 276 | ans = C8.V[x] + C8.V[y]; 277 | C8.V[x] = ans & 0xFF; 278 | C8.V[0xF] = (ans > 255); 279 | break; 280 | case 0x5: 281 | /* SUB Vx, Vy */ 282 | ans = C8.V[x] - C8.V[y]; 283 | carry = (C8.V[x] > C8.V[y]); 284 | C8.V[x] = ans & 0xFF; 285 | C8.V[0xF] = carry; 286 | break; 287 | case 0x6: 288 | /* SHR Vx, Vy */ 289 | if(!(quirks & QUIRKS_SHIFT)) 290 | C8.V[x] = C8.V[y]; 291 | carry = (C8.V[x] & 0x01); 292 | C8.V[x] >>= 1; 293 | C8.V[0xF] = carry; 294 | break; 295 | case 0x7: 296 | /* SUBN Vx, Vy */ 297 | ans = C8.V[y] - C8.V[x]; 298 | carry = (C8.V[y] > C8.V[x]); 299 | C8.V[x] = ans & 0xFF; 300 | C8.V[0xF] = carry; 301 | break; 302 | case 0xE: 303 | /* SHL Vx, Vy */ 304 | if(!(quirks & QUIRKS_SHIFT)) 305 | C8.V[x] = C8.V[y]; 306 | carry = ((C8.V[x] & 0x80) != 0); 307 | C8.V[x] <<= 1; 308 | C8.V[0xF] = carry; 309 | break; 310 | } 311 | } break; 312 | case 0x9000: 313 | /* SNE Vx, Vy */ 314 | if(C8.V[x] != C8.V[y]) C8.PC += 2; 315 | break; 316 | case 0xA000: 317 | /* LD I, nnn */ 318 | C8.I = nnn; 319 | break; 320 | case 0xB000: 321 | /* JP V0, nnn */ 322 | if(quirks & QUIRKS_JUMP) 323 | C8.PC = (nnn + C8.V[x]) & 0xFFF; 324 | else 325 | C8.PC = (nnn + C8.V[0]) & 0xFFF; 326 | break; 327 | case 0xC000: 328 | /* RND Vx, kk */ 329 | C8.V[x] = c8_rand() & kk; /* FIXME: Better RNG? */ 330 | break; 331 | case 0xD000: { 332 | /* DRW Vx, Vy, nibble */ 333 | int mW, mH, W, H, p, q; 334 | int tx, ty, byte, bit, pix; 335 | 336 | /* TODO: [17] mentions that V[x] and V[y] gets modified by 337 | this instruction... */ 338 | 339 | if(hi_res) { 340 | W = 128; H = 64; mW = 0x7F; mH = 0x3F; 341 | } else { 342 | W = 64; H = 32; mW = 0x3F; mH = 0x1F; 343 | } 344 | 345 | C8.V[0xF] = 0; 346 | if(nibble) { 347 | x = C8.V[x]; y = C8.V[y]; 348 | x &= mW; 349 | y &= mH; 350 | for(q = 0; q < nibble; q++) { 351 | ty = (y + q); 352 | if((quirks & QUIRKS_CLIPPING) && (ty >= H)) 353 | break; 354 | 355 | for(p = 0; p < 8; p++) { 356 | tx = (x + p); 357 | if((quirks & QUIRKS_CLIPPING) && (tx >= W)) 358 | break; 359 | pix = (C8.RAM[C8.I + q] & (0x80 >> p)) != 0; 360 | if(pix) { 361 | tx &= mW; 362 | ty &= mH; 363 | byte = ty * W + tx; 364 | bit = 1 << (byte & 0x07); 365 | byte >>= 3; 366 | if(pixels[byte] & bit) 367 | C8.V[0x0F] = 1; 368 | pixels[byte] ^= bit; 369 | } 370 | } 371 | } 372 | } else { 373 | /* SCHIP mode has a 16x16 sprite if nibble == 0 */ 374 | x = C8.V[x]; y = C8.V[y]; 375 | x &= mW; 376 | y &= mH; 377 | for(q = 0; q < 16; q++) { 378 | ty = (y + q); 379 | if((quirks & QUIRKS_CLIPPING) && (ty >= H)) 380 | break; 381 | 382 | for(p = 0; p < 16; p++) { 383 | tx = (x + p); 384 | if((quirks & QUIRKS_CLIPPING) && (tx >= W)) 385 | break; 386 | 387 | if(p >= 8) 388 | pix = (C8.RAM[C8.I + (q * 2) + 1] & (0x80 >> (p & 0x07))) != 0; 389 | else 390 | pix = (C8.RAM[C8.I + (q * 2)] & (0x80 >> p)) != 0; 391 | if(pix) { 392 | byte = ty * W + tx; 393 | bit = 1 << (byte & 0x07); 394 | byte >>= 3; 395 | if(pixels[byte] & bit) 396 | C8.V[0x0F] = 1; 397 | pixels[byte] ^= bit; 398 | } 399 | } 400 | } 401 | } 402 | screen_updated = 1; 403 | if(quirks & QUIRKS_DISP_WAIT) { 404 | yield = 1; 405 | } 406 | } break; 407 | case 0xE000: { 408 | if(kk == 0x9E) { 409 | /* SKP Vx */ 410 | if(keys & (1 << C8.V[x])) 411 | C8.PC += 2; 412 | } else if(kk == 0xA1) { 413 | /* SKNP Vx */ 414 | if(!(keys & (1 << C8.V[x]))) 415 | C8.PC += 2; 416 | } 417 | } break; 418 | case 0xF000: { 419 | switch(kk) { 420 | case 0x07: 421 | /* LD Vx, DT */ 422 | C8.V[x] = C8.DT; 423 | break; 424 | case 0x0A: { 425 | /* LD Vx, K */ 426 | if(!keys) { 427 | /* subsequent calls will encounter the Fx0A again */ 428 | C8.PC -= 2; 429 | return; 430 | } 431 | for(y = 0; y < 0xF; y++) { 432 | if(keys & (1 << y)) { 433 | C8.V[x] = y; 434 | break; 435 | } 436 | } 437 | keys = 0; 438 | } break; 439 | case 0x15: 440 | /* LD DT, Vx */ 441 | C8.DT = C8.V[x]; 442 | break; 443 | case 0x18: 444 | /* LD ST, Vx */ 445 | C8.ST = C8.V[x]; 446 | break; 447 | case 0x1E: 448 | /* ADD I, Vx */ 449 | C8.I += C8.V[x]; 450 | /* According to [wikipedia][] the VF is set if I overflows. */ 451 | if(C8.I > 0xFFF) { 452 | C8.V[0xF] = 1; 453 | C8.I &= 0xFFF; 454 | } else { 455 | C8.V[0xF] = 0; 456 | } 457 | break; 458 | case 0x29: 459 | /* LD F, Vx */ 460 | C8.I = FONT_OFFSET + (C8.V[x] & 0x0F) * 5; 461 | break; 462 | case 0x30: 463 | /* LD HF, Vx - Load 8x10 hi-resolution font */ 464 | C8.I = HFONT_OFFSET + (C8.V[x] & 0x0F) * 10; 465 | break; 466 | case 0x33: 467 | /* LD B, Vx */ 468 | C8.RAM[C8.I] = (C8.V[x] / 100) % 10; 469 | C8.RAM[C8.I + 1] = (C8.V[x] / 10) % 10; 470 | C8.RAM[C8.I + 2] = C8.V[x] % 10; 471 | break; 472 | case 0x55: 473 | /* LD [I], Vx */ 474 | if(C8.I + x > TOTAL_RAM) 475 | x = TOTAL_RAM - C8.I; 476 | assert(C8.I + x <= TOTAL_RAM); 477 | if(x >= 0) 478 | memcpy(C8.RAM + C8.I, C8.V, x+1); 479 | if(quirks & QUIRKS_MEM_CHIP8) 480 | C8.I += x + 1; 481 | break; 482 | case 0x65: 483 | /* LD Vx, [I] */ 484 | if(C8.I + x > TOTAL_RAM) 485 | x = TOTAL_RAM - C8.I; 486 | assert(C8.I + x <= TOTAL_RAM); 487 | if(x >= 0) 488 | memcpy(C8.V, C8.RAM + C8.I, x+1); 489 | if(quirks & QUIRKS_MEM_CHIP8) 490 | C8.I += x + 1; 491 | break; 492 | case 0x75: 493 | /* LD R, Vx */ 494 | assert(x <= sizeof hp48_flags); 495 | memcpy(hp48_flags, C8.V, x); 496 | break; 497 | case 0x85: 498 | /* LD Vx, R */ 499 | assert(x <= sizeof hp48_flags); 500 | memcpy(C8.V, hp48_flags, x); 501 | break; 502 | } 503 | } break; 504 | } 505 | } 506 | 507 | int c8_ended() { 508 | /* Check whether the next instruction is 00FD */ 509 | return borked || c8_opcode(C8.PC) == 0x00FD; 510 | } 511 | int c8_waitkey() { 512 | return (c8_opcode(C8.PC) & 0xF0FF) == 0xF00A; 513 | } 514 | 515 | uint8_t c8_get(uint16_t addr) { 516 | assert(addr < TOTAL_RAM); 517 | return C8.RAM[addr]; 518 | } 519 | 520 | void c8_set(uint16_t addr, uint8_t byte) { 521 | assert(addr < TOTAL_RAM); 522 | C8.RAM[addr] = byte; 523 | } 524 | 525 | uint16_t c8_opcode(uint16_t addr) { 526 | assert(addr < TOTAL_RAM - 1); 527 | return C8.RAM[addr] << 8 | C8.RAM[addr+1]; 528 | } 529 | 530 | uint16_t c8_get_pc() { 531 | return C8.PC; 532 | } 533 | 534 | uint16_t c8_prog_size() { 535 | uint16_t n; 536 | for(n = TOTAL_RAM - 1; n > PROG_OFFSET && C8.RAM[n] == 0; n--); 537 | if(++n & 0x1) // Fix for #4 538 | return n + 1; 539 | return n; 540 | } 541 | 542 | uint8_t c8_get_reg(uint8_t r) { 543 | if(r > 0xF) return 0; 544 | return C8.V[r]; 545 | } 546 | 547 | int c8_screen_updated() { 548 | return screen_updated; 549 | } 550 | 551 | int c8_resolution(int *w, int *h) { 552 | if(!w || !h) return hi_res; 553 | if(hi_res) { 554 | *w = 128; *h = 64; 555 | } else { 556 | *w = 64; *h = 32; 557 | } 558 | return hi_res; 559 | } 560 | 561 | int c8_get_pixel(int x, int y) { 562 | int byte, bit, w, h; 563 | if(hi_res) { 564 | w = 128; h = 64; 565 | } else { 566 | w = 64; h = 32; 567 | } 568 | if(x < 0 || x >= w || y < 0 || y >= h) return 0; 569 | byte = y * w + x; 570 | bit = byte & 0x07; 571 | byte >>= 3; 572 | assert(byte < sizeof pixels); 573 | assert(bit < 8); 574 | return (pixels[byte] & (1 << bit)) != 0; 575 | } 576 | 577 | void c8_key_down(uint8_t k) { 578 | if(k > 0xF) return; 579 | keys |= 1 << k; 580 | } 581 | 582 | void c8_key_up(uint8_t k) { 583 | if(k > 0xF) return; 584 | keys &= ~(1 << k); 585 | } 586 | 587 | void c8_60hz_tick() { 588 | yield = 0; 589 | if(C8.DT > 0) C8.DT--; 590 | if(C8.ST > 0) C8.ST--; 591 | } 592 | 593 | int c8_sound() { 594 | return C8.ST > 0; 595 | } 596 | 597 | size_t c8_load_program(uint8_t program[], size_t n) { 598 | if(n + PROG_OFFSET > TOTAL_RAM) 599 | n = TOTAL_RAM - PROG_OFFSET; 600 | assert(n + PROG_OFFSET <= TOTAL_RAM); 601 | memcpy(C8.RAM + PROG_OFFSET, program, n); 602 | return n; 603 | } 604 | 605 | int c8_load_file(const char *fname) { 606 | FILE *f; 607 | size_t len, r; 608 | if(!(f = fopen(fname, "rb"))) 609 | return 0; 610 | fseek(f, 0, SEEK_END); 611 | len = ftell(f); 612 | if(len == 0 || len + PROG_OFFSET > TOTAL_RAM) { 613 | fclose(f); 614 | return 0; 615 | } 616 | rewind(f); 617 | r = fread(C8.RAM + PROG_OFFSET, 1, len, f); 618 | fclose(f); 619 | if(r != len) 620 | return 0; 621 | return len; 622 | } 623 | 624 | char *c8_load_txt(const char *fname) { 625 | FILE *f; 626 | size_t len, r; 627 | char *bytes; 628 | 629 | if(!(f = fopen(fname, "rb"))) 630 | return NULL; 631 | 632 | fseek(f, 0, SEEK_END); 633 | len = ftell(f); 634 | rewind(f); 635 | 636 | if(!(bytes = malloc(len+2))) 637 | return NULL; 638 | r = fread(bytes, 1, len, f); 639 | 640 | if(r != len) { 641 | free(bytes); 642 | return NULL; 643 | } 644 | 645 | fclose(f); 646 | bytes[len] = '\0'; 647 | 648 | return bytes; 649 | } 650 | 651 | int c8_save_file(const char *fname) { 652 | uint16_t n = c8_prog_size(); 653 | size_t len = n - PROG_OFFSET; 654 | FILE *f = fopen(fname, "wb"); 655 | if(!f) 656 | return 0; 657 | if(fwrite(C8.RAM + PROG_OFFSET, 1, len, f) != len) 658 | return 0; 659 | fclose(f); 660 | return len; 661 | } 662 | 663 | int c8_message(const char *msg, ...) { 664 | if(msg) { 665 | va_list arg; 666 | va_start (arg, msg); 667 | vsnprintf (c8_message_text, MAX_MESSAGE_TEXT-1, msg, arg); 668 | va_end (arg); 669 | } 670 | if(c8_puts) 671 | return c8_puts(c8_message_text); 672 | return 0; 673 | } 674 | 675 | /** 676 | * [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8 677 | * 678 | */ 679 | -------------------------------------------------------------------------------- /sdl/pocadv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Pocket Adventure 3 | * ================ 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "pocadv.h" 17 | 18 | #if SCREEN_SCALE == 0 19 | /* This has happened to me more than once */ 20 | # error "You set SCREEN_SCALE to 0 again, dummy!" 21 | #endif 22 | 23 | #define VSCREEN_WIDTH (SCREEN_WIDTH * (EPX_SCALE?2:1)) 24 | #define VSCREEN_HEIGHT (SCREEN_HEIGHT * (EPX_SCALE?2:1)) 25 | #define WINDOW_WIDTH (VSCREEN_WIDTH * SCREEN_SCALE) 26 | #define WINDOW_HEIGHT (VSCREEN_HEIGHT * SCREEN_SCALE) 27 | 28 | #ifndef USE_LOG_STREAM 29 | # define USE_LOG_STREAM 0 30 | #else 31 | # ifndef LOG_STREAM 32 | # define LOG_STREAM stderr 33 | # endif 34 | #endif 35 | 36 | #ifndef LOG_FILE_NAME 37 | # define LOG_FILE_NAME "sdl-game.log" 38 | #endif 39 | 40 | #ifndef SDL2 41 | static SDL_Surface *window; 42 | #else 43 | static SDL_Renderer *renderer = NULL; 44 | static SDL_Texture *texture = NULL; 45 | static SDL_Window *window; 46 | #endif 47 | 48 | Bitmap *screen; 49 | #ifndef SDL2 50 | Bitmap *vscreen; 51 | #endif 52 | 53 | #ifndef SDL2 54 | char keys[SDLK_LAST]; 55 | #else 56 | char keys[SDL_NUM_SCANCODES]; 57 | #endif 58 | static int pressed_key = 0; 59 | 60 | int mouse_x, mouse_y; 61 | static int mclick = 0, mdown = 0, mrelease = 0, mmove = 0; 62 | 63 | static Bitmap *cursor = NULL; 64 | static int cursor_hsx, cursor_hsy; 65 | static Bitmap *cursor_back = NULL; 66 | 67 | #if EPX_SCALE 68 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out); 69 | static Bitmap *epx; 70 | #endif 71 | 72 | static int dodebug = 0; 73 | static double frameTimes[256]; 74 | static Uint32 n_elapsed = 0; 75 | 76 | int quit = 0; 77 | 78 | /* This leaves a bit to be desired if I'm to 79 | support multi-touch on mobile eventually */ 80 | int mouse_clicked() { 81 | return mclick; 82 | } 83 | int mouse_down() { 84 | return mdown; 85 | } 86 | int mouse_released() { 87 | return mrelease; 88 | } 89 | int mouse_moved() { 90 | return mmove; 91 | } 92 | int key_pressed() { 93 | return pressed_key; 94 | } 95 | 96 | int show_debug() { 97 | // #ifdef NDEBUG 98 | // return 0; 99 | // #else 100 | return dodebug; 101 | // #endif 102 | } 103 | 104 | /* Handle special keys */ 105 | #ifdef SDL2 106 | static int handleKeys(SDL_Scancode key) { 107 | #else 108 | static int handleKeys(SDLKey key) { 109 | #endif 110 | switch(key) { 111 | #if !defined(__EMSCRIPTEN__) && ESCAPE_QUITS 112 | case KCODE(ESCAPE) : quit = 1; return 1; 113 | #endif 114 | /* TODO: F11 for fullscreen, etc. */ 115 | #if !defined(__EMSCRIPTEN__) 116 | case KCODE(F10): dodebug = !dodebug; return 1; 117 | case KCODE(F12): { 118 | /* F12 for screenshots; Doesn't make sense in the browser. */ 119 | char filename[128]; 120 | 121 | #ifdef _MSC_VER 122 | time_t t = time(NULL); 123 | struct tm buf, *ptr = &buf; 124 | localtime_s(ptr, &t); 125 | #else 126 | time_t t; 127 | struct tm *ptr; 128 | t = time(NULL); 129 | ptr = localtime(&t); 130 | #endif 131 | strftime(filename, sizeof filename, "screen-%Y%m%d%H%M%S.bmp", ptr); 132 | 133 | bm_save(screen, filename); 134 | } return 1; 135 | #endif 136 | default : return 0; 137 | } 138 | } 139 | 140 | char *readfile(const char *fname) { 141 | FILEOBJ *f; 142 | long len,r; 143 | char *str; 144 | 145 | if(!(f = FOPEN(fname, "rb"))) 146 | return NULL; 147 | 148 | FSEEK(f, 0, SEEK_END); 149 | len = (long)FTELL(f); 150 | REWIND(f); 151 | 152 | if(!(str = malloc(len+2))) 153 | return NULL; 154 | r = FREAD(str, 1, len, f); 155 | 156 | if(r != len) { 157 | free(str); 158 | return NULL; 159 | } 160 | 161 | FCLOSE(f); 162 | str[len] = '\0'; 163 | return str; 164 | } 165 | 166 | void set_cursor(Bitmap *b, int hsx, int hsy) { 167 | cursor_hsx = hsx; 168 | cursor_hsy = hsy; 169 | cursor = b; 170 | int w = bm_width(b), h= bm_height(b); 171 | if(b) { 172 | if(!cursor_back) 173 | cursor_back = bm_create(w, h); 174 | else if(bm_width(cursor_back) != w || bm_height(cursor_back) != h) { 175 | bm_free(cursor_back); 176 | cursor_back = bm_create(w, h); 177 | } 178 | SDL_ShowCursor(SDL_DISABLE); 179 | } else { 180 | bm_free(cursor_back); 181 | cursor_back = NULL; 182 | SDL_ShowCursor(SDL_ENABLE); 183 | } 184 | } 185 | 186 | static const char *lastEvent = "---"; 187 | static int finger_id = -1; 188 | 189 | static void handle_events() { 190 | 191 | SDL_Event event; 192 | 193 | while(SDL_PollEvent(&event)) { 194 | switch(event.type) { 195 | case SDL_KEYDOWN: { 196 | lastEvent = "Key Down"; finger_id=-1; 197 | #ifdef SDL2 198 | if(handleKeys(event.key.keysym.scancode)) 199 | break; 200 | keys[event.key.keysym.scancode] = 1; 201 | pressed_key = event.key.keysym.sym; 202 | if(!(pressed_key & 0x40000000)) { 203 | if(event.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) { 204 | if(isalpha(pressed_key)) { 205 | pressed_key = toupper(pressed_key); 206 | } else { 207 | /* This would not work with different keyboard layouts */ 208 | static const char *in = "`1234567890-=[],./;'\\"; 209 | static const char *out = "~!@#$%^&*()_+{}<>?:\"|"; 210 | char *p = strchr(in, pressed_key); 211 | if(p) { 212 | pressed_key = out[p-in]; 213 | } 214 | } 215 | } else if (pressed_key == SDLK_DELETE) { 216 | // The Del key is a bit special... 217 | pressed_key = 0x40000000 | SDL_SCANCODE_DELETE; 218 | } 219 | } 220 | #else 221 | if(handleKeys(event.key.keysym.sym)) 222 | break; 223 | keys[event.key.keysym.sym] = 1; 224 | pressed_key = event.key.keysym.sym; 225 | if(pressed_key > 0xFF) { 226 | pressed_key |= 0x40000000; 227 | } else if(event.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) { 228 | if(isalpha(pressed_key)) { 229 | pressed_key = toupper(pressed_key); 230 | } else { 231 | /* This would not work with different keyboard layouts */ 232 | static const char *in = "`1234567890-=[],./;'\\"; 233 | static const char *out = "~!@#$%^&*()_+{}<>?:\"|"; 234 | char *p = strchr(in, pressed_key); 235 | if(p) { 236 | pressed_key = out[p-in]; 237 | } 238 | } 239 | } else if (pressed_key == SDLK_DELETE) { 240 | pressed_key = 0x40000000 | SDLK_DELETE; 241 | } 242 | #endif 243 | } break; 244 | case SDL_KEYUP: { 245 | lastEvent = "Key Up";finger_id=-1; 246 | #ifdef SDL2 247 | keys[event.key.keysym.scancode] = 0; 248 | #else 249 | keys[event.key.keysym.sym] = 0; 250 | #endif 251 | } break; 252 | #ifndef ANDROID /* Ignore the mouse on android, that's what the touch events are for */ 253 | case SDL_MOUSEBUTTONDOWN: { 254 | lastEvent = "Mouse Down";finger_id = 0; 255 | if(event.button.button != SDL_BUTTON_LEFT) break; 256 | mdown = 1; 257 | mrelease = 0; 258 | mclick = 1; 259 | } break; 260 | case SDL_MOUSEBUTTONUP: { 261 | lastEvent = "Mouse Up";finger_id = 0; 262 | if(event.button.button != SDL_BUTTON_LEFT) break; 263 | mdown = 0; 264 | mrelease = 1; 265 | } break; 266 | case SDL_MOUSEMOTION: { 267 | lastEvent = "Mouse Move";finger_id = 0; 268 | mouse_x = event.button.x * SCREEN_WIDTH / WINDOW_WIDTH; 269 | mouse_y = event.button.y * SCREEN_HEIGHT / WINDOW_HEIGHT; 270 | mmove = 1; 271 | } break; 272 | #endif 273 | #if defined(SDL2) && defined(ANDROID) 274 | case SDL_FINGERDOWN: { 275 | lastEvent = "Finger Down";finger_id=event.tfinger.fingerId; 276 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT); 277 | if(!pointer_click(x, y,finger_id)) { 278 | pointer_down(x, y,finger_id); 279 | } 280 | } break; 281 | case SDL_FINGERUP: { 282 | lastEvent = "Finger Up";finger_id=event.tfinger.fingerId; 283 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT); 284 | pointer_up(x, y,finger_id); 285 | } break; 286 | case SDL_FINGERMOTION: { 287 | lastEvent = "Finger Motion";finger_id=event.tfinger.fingerId; 288 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT); 289 | pointer_move(x, y,finger_id); 290 | } break; 291 | #endif 292 | case SDL_QUIT: { 293 | quit = 1; 294 | } break; 295 | } 296 | } 297 | } 298 | 299 | Bitmap *get_bmp(const char *filename) { 300 | #ifdef ANDROID 301 | SDL_RWops *file = SDL_RWFromFile(filename, "rb"); 302 | Bitmap *bmp = bm_load_rw(file); 303 | SDL_RWclose(file); 304 | #else 305 | Bitmap *bmp = bm_load(filename); 306 | #endif 307 | return bmp; 308 | } 309 | 310 | static void draw_frame() { 311 | static Uint32 start = 0; 312 | static Uint32 elapsed = 0; 313 | 314 | elapsed = SDL_GetTicks() - start; 315 | 316 | /* It is technically possible for the game to run 317 | too fast, rendering the deltaTime useless */ 318 | if(elapsed < 10) 319 | return; 320 | 321 | double deltaTime = elapsed / 1000.0; 322 | if(!render(deltaTime)) { 323 | quit = 1; 324 | } 325 | 326 | start = SDL_GetTicks(); 327 | 328 | if(dodebug && n_elapsed > 0) { 329 | double sum = 0; 330 | int i, n = n_elapsed > 0xFF ? 0xFF : n_elapsed; 331 | for(i = 0; i < n; i++) sum += frameTimes[i]; 332 | double avg = sum / n; 333 | double fps = 1.0 / avg; 334 | BmFont *save = bm_get_font(screen); 335 | bm_reset_font(screen); 336 | bm_set_color(screen, bm_atoi("red")); 337 | bm_fillrect(screen, 0, 0, 50, 10); 338 | bm_set_color(screen, bm_atoi("yellow")); 339 | bm_printf(screen, 1, 1, "%3.2f", fps); 340 | bm_set_font(screen, save); 341 | } 342 | frameTimes[(n_elapsed++) & 0xFF] = deltaTime; 343 | 344 | #if EPX_SCALE 345 | scale_epx_i(screen, epx); 346 | #endif 347 | 348 | mclick = 0; 349 | mrelease = 0; 350 | mmove = 0; 351 | pressed_key = 0; 352 | } 353 | 354 | #define USE_SDL_LOG 0 355 | 356 | static FILE *logfile = NULL; 357 | 358 | void rlog(const char *fmt, ...) { 359 | va_list arg; 360 | va_start(arg, fmt); 361 | #if defined(SDL2) && USE_SDL_LOG 362 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, arg); 363 | #else 364 | fputs("INFO: ", logfile); 365 | vfprintf(logfile, fmt, arg); 366 | fputc('\n', logfile); 367 | #endif 368 | va_end(arg); 369 | } 370 | 371 | void rerror(const char *fmt, ...) { 372 | va_list arg; 373 | va_start(arg, fmt); 374 | #if defined(SDL2) && USE_SDL_LOG 375 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, fmt, arg); 376 | #else 377 | fputs("ERROR: ", logfile); 378 | vfprintf(logfile, fmt, arg); 379 | fputc('\n', logfile); 380 | #endif 381 | va_end(arg); 382 | } 383 | 384 | void exit_error(const char *fmt, ...) { 385 | if(fmt) { 386 | va_list arg; 387 | va_start (arg, fmt); 388 | #if defined(SDL2) && USE_SDL_LOG 389 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, fmt, arg); 390 | #else 391 | fputs("ERROR: ", logfile); 392 | vfprintf (logfile, fmt, arg); 393 | #endif 394 | va_end (arg); 395 | } 396 | if(logfile != stdout && logfile != stderr) 397 | fclose(logfile); 398 | else { 399 | fflush(stdout); 400 | fflush(stderr); 401 | } 402 | exit(1); 403 | } 404 | 405 | static void do_iteration() { 406 | int cx = 0, cy = 0; 407 | 408 | #ifndef SDL2 409 | if(SDL_MUSTLOCK(window)) 410 | SDL_LockSurface(window); 411 | bm_rebind(vscreen, window->pixels); 412 | handle_events(); 413 | 414 | draw_frame(); 415 | 416 | if(cursor) { 417 | cx = mouse_x - cursor_hsx; 418 | cy = mouse_y - cursor_hsy; 419 | 420 | int cw = bm_width(cursor), ch = bm_height(cursor); 421 | 422 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch); 423 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch); 424 | } 425 | 426 | # if EPX_SCALE 427 | bm_blit_ex(vscreen, 0, 0, bm_width(vscreen), bm_height(vscreen), epx, 0, 0, bm_width(epx), bm_height(epx), 0); 428 | # else 429 | bm_blit_ex(vscreen, 0, 0, bm_width(vscreen), bm_height(vscreen), screen, 0, 0, bm_width(screen), bm_height(screen), 0); 430 | # endif 431 | 432 | if(SDL_MUSTLOCK(window)) 433 | SDL_UnlockSurface(window); 434 | SDL_Flip(window); 435 | #else 436 | handle_events(); 437 | 438 | draw_frame(); 439 | 440 | if(cursor) { 441 | cx = mouse_x - cursor_hsx; 442 | cy = mouse_y - cursor_hsy; 443 | int cw = bm_width(cursor), ch = bm_height(cursor); 444 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch); 445 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch); 446 | } 447 | 448 | # if EPX_SCALE 449 | SDL_UpdateTexture(texture, NULL, bm_data(epx), bm_width(epx)*4); 450 | # else 451 | SDL_UpdateTexture(texture, NULL, bm_raw_data(screen), bm_width(screen)*4); 452 | # endif 453 | SDL_RenderClear(renderer); 454 | SDL_RenderCopy(renderer, texture, NULL, NULL); 455 | SDL_RenderPresent(renderer); 456 | #endif 457 | 458 | if(cursor) { 459 | bm_maskedblit(screen, cx, cy, cursor_back, 0, 0, bm_width(cursor_back), bm_height(cursor_back)); 460 | } 461 | } 462 | 463 | int main(int argc, char *argv[]) { 464 | 465 | #ifdef __EMSCRIPTEN__ 466 | logfile = stdout; 467 | #else 468 | //logfile = fopen("pocadv.log", "w"); 469 | 470 | # ifdef _MSC_VER 471 | errno_t err = fopen_s(&logfile, LOG_FILE_NAME, "w"); 472 | if (err != 0) 473 | return 1; 474 | # elif USE_LOG_STREAM 475 | logfile = LOG_STREAM; 476 | # else 477 | logfile = fopen(LOG_FILE_NAME, "w"); 478 | if (!logfile) 479 | return 1; 480 | # endif 481 | #endif 482 | 483 | rlog("%s: Application Running", WINDOW_CAPTION); 484 | 485 | srand((unsigned int)time(NULL)); 486 | 487 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); 488 | 489 | #ifdef SDL2 490 | window = SDL_CreateWindow(WINDOW_CAPTION " - SDL2", 491 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 492 | WINDOW_WIDTH, WINDOW_HEIGHT, 493 | SDL_WINDOW_SHOWN); 494 | if(!window) { 495 | rerror("%s","SDL_CreateWindow()"); 496 | return 0; 497 | } 498 | 499 | renderer = SDL_CreateRenderer(window, -1, 0); 500 | if(!renderer) { 501 | rerror("%s","SDL_CreateRenderer()"); 502 | return 1; 503 | } 504 | 505 | # if EPX_SCALE 506 | epx = bm_create(VSCREEN_WIDTH, VSCREEN_HEIGHT); 507 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT); 508 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, VSCREEN_WIDTH, VSCREEN_HEIGHT); 509 | if(!texture) { 510 | rerror("%s","SDL_CreateTexture()"); 511 | return 1; 512 | } 513 | # else 514 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT); 515 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); 516 | if(!texture) { 517 | rerror("%s","SDL_CreateTexture()"); 518 | return 1; 519 | } 520 | # endif 521 | 522 | init_game(argc, argv); 523 | #else 524 | /* Using SDL 1.2 */ 525 | SDL_WM_SetCaption(WINDOW_CAPTION " - SDL1.2", "game"); 526 | window = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32, SDL_SWSURFACE); 527 | 528 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT); 529 | # if EPX_SCALE 530 | epx = bm_create(VSCREEN_WIDTH, VSCREEN_HEIGHT); 531 | # endif 532 | if(SDL_MUSTLOCK(window)) { 533 | SDL_LockSurface(window); 534 | vscreen = bm_bind(WINDOW_WIDTH, WINDOW_HEIGHT, window->pixels); 535 | init_game(argc, argv); 536 | SDL_UnlockSurface(window); 537 | } else { 538 | vscreen = bm_bind(WINDOW_WIDTH, WINDOW_HEIGHT, window->pixels); 539 | init_game(argc, argv); 540 | } 541 | 542 | SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); 543 | #endif 544 | 545 | #ifdef TEST_SDL_LOCK_OPTS 546 | EM_ASM("SDL.defaults.copyOnLock = false; SDL.defaults.discardOnLock = true; SDL.defaults.opaqueFrontBuffer = false"); 547 | #endif 548 | 549 | #ifdef __EMSCRIPTEN__ 550 | emscripten_set_main_loop(do_iteration, 0, 1); 551 | #else 552 | rlog("%s: Entering main loop", WINDOW_CAPTION); 553 | 554 | while(!quit) { 555 | do_iteration(); 556 | } 557 | 558 | deinit_game(); 559 | 560 | #endif 561 | rlog("%s: Main loop stopped", WINDOW_CAPTION); 562 | #ifdef SDL2 563 | SDL_DestroyTexture(texture); 564 | SDL_DestroyRenderer(renderer); 565 | SDL_DestroyWindow(window); 566 | bm_free(screen); 567 | # if EPX_SCALE 568 | bm_free(epx); 569 | # endif 570 | 571 | #else 572 | bm_unbind(vscreen); 573 | bm_free(screen); 574 | # if EPX_SCALE 575 | bm_free(epx); 576 | # endif 577 | 578 | #endif 579 | 580 | SDL_Quit(); 581 | 582 | rlog("%s","Application Done!\n"); 583 | #if !USE_LOG_STREAM 584 | fclose(logfile); 585 | #endif 586 | return 0; 587 | } 588 | 589 | /* EPX 2x scaling */ 590 | #if EPX_SCALE 591 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out) { 592 | int x, y, mx = in->w, my = in->h; 593 | if(!out) return NULL; 594 | if(!in) return out; 595 | if(out->w < (mx << 1)) mx = (out->w - 1) >> 1; 596 | if(out->h < (my << 1)) my = (out->h - 1) >> 1; 597 | for(y = 0; y < my; y++) { 598 | for(x = 0; x < mx; x++) { 599 | unsigned int P = bm_get(in, x, y); 600 | unsigned int A = (y > 0) ? bm_get(in, x, y - 1) : P; 601 | unsigned int B = (x < in->w - 1) ? bm_get(in, x + 1, y) : P; 602 | unsigned int C = (x > 0) ? bm_get(in, x - 1, y) : P; 603 | unsigned int D = (y < in->h - 1) ? bm_get(in, x, y + 1) : P; 604 | 605 | unsigned int P1 = P, P2 = P, P3 = P, P4 = P; 606 | 607 | if(C == A && C != D && A != B) P1 = A; 608 | if(A == B && A != C && B != D) P2 = B; 609 | if(B == D && B != A && D != C) P4 = D; 610 | if(D == C && D != B && C != A) P3 = C; 611 | 612 | bm_set(out, (x << 1), (y << 1), P1); 613 | bm_set(out, (x << 1) + 1, (y << 1), P2); 614 | bm_set(out, (x << 1), (y << 1) + 1, P3); 615 | bm_set(out, (x << 1) + 1, (y << 1) + 1, P4); 616 | } 617 | } 618 | return out; 619 | } 620 | #endif 621 | -------------------------------------------------------------------------------- /gdi/gdi.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Win32 application that allows rendering directly to a GDI contexts 3 | * through my bitmap module. 4 | * (for simple games without any third party dependencies) 5 | * 6 | * ## References: 7 | * 8 | * * The bitmap module: https://github.com/wernsey/bitmap 9 | * * https://www.daniweb.com/software-development/cpp/code/241875/fast-animation-with-the-windows-gdi 10 | * * https://www-user.tu-chemnitz.de/~heha/petzold/ch14e.htm 11 | * * https://www-user.tu-chemnitz.de/~heha/petzold/ch15d.htm 12 | * * http://forums.codeguru.com/showthread.php?487633-32-bit-DIB-from-24-bit-bitmap 13 | * * HELLO_WIN.C example of the tcc C compiler 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifndef WIN32_LEAN_AND_MEAN 23 | #define WIN32_LEAN_AND_MEAN 24 | #endif 25 | 26 | #define __STDC_WANT_LIB_EXT1__ 1 27 | 28 | #include 29 | 30 | #include "../bmp.h" 31 | #include "gdi.h" 32 | 33 | #define VSCREEN_WIDTH (SCREEN_WIDTH * (EPX_SCALE?2:1)) 34 | #define VSCREEN_HEIGHT (SCREEN_HEIGHT * (EPX_SCALE?2:1)) 35 | #define WINDOW_WIDTH (VSCREEN_WIDTH * SCREEN_SCALE) 36 | #define WINDOW_HEIGHT (VSCREEN_HEIGHT * SCREEN_SCALE) 37 | 38 | /* fflush() the log file after each call to rlog()? 39 | I only use it for those hard to debug crashes */ 40 | #ifndef FLUSH 41 | # define FLUSH 0 42 | #endif 43 | 44 | #ifndef LOG_FILE_NAME 45 | # define LOG_FILE_NAME "gdi-game.log" 46 | #endif 47 | 48 | #ifndef USE_LOG_STREAM 49 | # define USE_LOG_STREAM 0 50 | #else 51 | # ifndef LOG_STREAM 52 | # define LOG_STREAM stderr 53 | # endif 54 | #endif 55 | 56 | static char szAppName[] = WINDOW_CAPTION; 57 | static char szTitle[] = WINDOW_CAPTION " - GDI"; 58 | 59 | Bitmap *screen = NULL; 60 | 61 | static Bitmap *cursor = NULL; 62 | static int cursor_hsx, cursor_hsy; 63 | static Bitmap *cursor_back = NULL; 64 | 65 | /* Virtual-Key Codes here: 66 | https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx */ 67 | #define MAX_KEYS 256 68 | char keys[MAX_KEYS]; 69 | static int pressed_key = 0; 70 | 71 | int mouse_x, mouse_y; 72 | static int mclick = 0, mdown = 0, mrelease = 0, mmove = 0; 73 | 74 | int quit = 0; 75 | 76 | static int show_fps = 0; 77 | static double frameTimes[256]; 78 | static unsigned int n_elapsed = 0; 79 | 80 | int show_debug() { 81 | return show_fps; 82 | } 83 | 84 | #if EPX_SCALE 85 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out); 86 | #endif 87 | 88 | void clear_keys() { 89 | int i; 90 | for(i = 0; i < MAX_KEYS; i++) { 91 | keys[i] = 0; 92 | } 93 | } 94 | int mouse_clicked() { 95 | return mclick; 96 | } 97 | int mouse_down() { 98 | return mdown; 99 | } 100 | int mouse_released() { 101 | return mrelease; 102 | } 103 | int mouse_moved() { 104 | return mmove; 105 | } 106 | int key_pressed() { 107 | return pressed_key; 108 | } 109 | 110 | static FILE *logfile = NULL; 111 | 112 | void rlog(const char *fmt, ...) { 113 | va_list arg; 114 | va_start(arg, fmt); 115 | fputs("INFO: ", logfile); 116 | vfprintf(logfile, fmt, arg); 117 | fputc('\n', logfile); 118 | va_end(arg); 119 | #if FLUSH 120 | fflush(logfile); 121 | #endif 122 | } 123 | 124 | void rerror(const char *fmt, ...) { 125 | va_list arg; 126 | va_start(arg, fmt); 127 | fputs("ERROR: ", logfile); 128 | vfprintf(logfile, fmt, arg); 129 | fputc('\n', logfile); 130 | va_end(arg); 131 | fflush(logfile); 132 | } 133 | 134 | void exit_error(const char *msg, ...) { 135 | char message_text[256]; 136 | if(msg) { 137 | va_list arg; 138 | va_start (arg, msg); 139 | vsnprintf (message_text, (sizeof message_text) - 1, msg, arg); 140 | message_text[(sizeof message_text) - 1] = '\0'; 141 | va_end (arg); 142 | fputc('\n', logfile); 143 | } else { 144 | message_text[0] = '\0'; 145 | } 146 | if (logfile) { 147 | fputs(message_text, logfile); 148 | } 149 | MessageBox( 150 | NULL, 151 | message_text, 152 | "Error", 153 | MB_ICONERROR | MB_OK 154 | ); 155 | exit(1); 156 | } 157 | 158 | void set_cursor(Bitmap *b, int hsx, int hsy) { 159 | cursor_hsx = hsx; 160 | cursor_hsy = hsy; 161 | cursor = b; 162 | if(b) { 163 | int w = bm_width(b), h = bm_height(b); 164 | if(!cursor_back) 165 | cursor_back = bm_create(w, h); 166 | else if(bm_width(cursor_back) != w || bm_height(cursor_back) != h) { 167 | bm_free(cursor_back); 168 | cursor_back = bm_create(w, h); 169 | } 170 | ShowCursor(0); 171 | } else { 172 | bm_free(cursor_back); 173 | cursor_back = NULL; 174 | ShowCursor(1); 175 | } 176 | } 177 | 178 | /** WIN32 and GDI routines below this line *****************************************/ 179 | 180 | static int split_cmd_line(char *cmdl, char *argv[], int max); 181 | static void FitWindow(HWND hWnd); 182 | 183 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 184 | { 185 | static HDC hdc, hdcMem; 186 | static HBITMAP hbmOld, hbmp; 187 | 188 | #if EPX_SCALE 189 | static Bitmap *epx = NULL; 190 | #endif 191 | 192 | #define MAX_ARGS 16 193 | static int argc = 0; 194 | static char *argv[MAX_ARGS]; 195 | static LPTSTR cmdl; 196 | 197 | switch (message) { 198 | 199 | case WM_CREATE: { 200 | unsigned char *pixels; 201 | 202 | BITMAPINFO bmi; 203 | ZeroMemory(&bmi, sizeof bmi); 204 | bmi.bmiHeader.biSize = sizeof(BITMAPINFO); 205 | bmi.bmiHeader.biWidth = VSCREEN_WIDTH; 206 | bmi.bmiHeader.biHeight = -VSCREEN_HEIGHT; // Order pixels from top to bottom 207 | bmi.bmiHeader.biPlanes = 1; 208 | bmi.bmiHeader.biBitCount = 32; // last byte not used, 32 bit for alignment 209 | bmi.bmiHeader.biCompression = BI_RGB; 210 | bmi.bmiHeader.biSizeImage = 0; 211 | bmi.bmiHeader.biXPelsPerMeter = 0; 212 | bmi.bmiHeader.biYPelsPerMeter = 0; 213 | bmi.bmiHeader.biClrUsed = 0; 214 | bmi.bmiHeader.biClrImportant = 0; 215 | bmi.bmiColors[0].rgbBlue = 0; 216 | bmi.bmiColors[0].rgbGreen = 0; 217 | bmi.bmiColors[0].rgbRed = 0; 218 | bmi.bmiColors[0].rgbReserved = 0; 219 | 220 | hdc = GetDC( hwnd ); 221 | hbmp = CreateDIBSection( hdc, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, 0 ); 222 | if (!hbmp) { 223 | exit_error("CreateDIBSection"); 224 | return 0; 225 | } 226 | 227 | hdcMem = CreateCompatibleDC( hdc ); 228 | hbmOld = (HBITMAP)SelectObject( hdcMem, hbmp ); 229 | 230 | #if !EPX_SCALE 231 | screen = bm_bind(VSCREEN_WIDTH, VSCREEN_HEIGHT, pixels); 232 | #else 233 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT); 234 | epx = bm_bind(VSCREEN_WIDTH, VSCREEN_HEIGHT, pixels); 235 | #endif 236 | bm_set_color(screen, bm_atoi("black")); 237 | bm_clear(screen); 238 | 239 | clear_keys(); 240 | 241 | cmdl = _strdup(GetCommandLine()); 242 | argc = split_cmd_line(cmdl, argv, MAX_ARGS); 243 | 244 | init_game(argc, argv); 245 | 246 | } break; 247 | 248 | case WM_DESTROY: 249 | quit = 1; 250 | deinit_game(); 251 | 252 | free(cmdl); 253 | 254 | #if !EPX_SCALE 255 | bm_unbind(screen); 256 | #else 257 | bm_free(screen); 258 | bm_unbind(epx); 259 | #endif 260 | SelectObject( hdcMem, hbmOld ); 261 | DeleteDC( hdc ); 262 | screen = NULL; 263 | PostQuitMessage(0); 264 | break; 265 | 266 | case WM_RBUTTONUP: 267 | #if 0 268 | DestroyWindow(hwnd); 269 | #endif 270 | break; 271 | 272 | /* If you want text input, WM_CHAR is what you're looking for */ 273 | case WM_CHAR: 274 | if (wParam < 128) { 275 | pressed_key = wParam; 276 | } 277 | break; 278 | case WM_SYSKEYDOWN: 279 | // TIL the F10 key doesn't go through the WM_KEYDOWN: 280 | // https://msdn.microsoft.com/en-us/library/windows/desktop/gg153546(v=vs.85).aspx 281 | if (wParam == VK_F10) { 282 | show_fps = !show_fps; 283 | } else return DefWindowProc(hwnd, message, wParam, lParam); 284 | break; 285 | case WM_KEYDOWN: 286 | if (wParam == VK_F12) { 287 | 288 | char filename[128]; 289 | #ifdef _MSC_VER 290 | time_t t = time(NULL); 291 | struct tm buf, *ptr = &buf; 292 | localtime_s(ptr, &t); 293 | #else 294 | time_t t; 295 | struct tm *ptr; 296 | t = time(NULL); 297 | ptr = localtime(&t); 298 | #endif 299 | strftime(filename, sizeof filename, "screen-%Y%m%d%H%M%S.bmp", ptr); 300 | bm_save(screen, filename); 301 | 302 | } else if (ESCAPE_QUITS && VK_ESCAPE == wParam) { 303 | DestroyWindow(hwnd); 304 | } else if (wParam < MAX_KEYS) { 305 | keys[wParam] = 1; 306 | pressed_key = wParam | 0xFFFF0000; 307 | } 308 | break; 309 | case WM_KEYUP: 310 | if (wParam < MAX_KEYS) { 311 | keys[wParam] = 0; 312 | } 313 | break; 314 | 315 | case WM_LBUTTONDOWN: 316 | case WM_LBUTTONDBLCLK: /* ...all clicks treated equally */ 317 | { 318 | mouse_x = LOWORD(lParam) * SCREEN_WIDTH / WINDOW_WIDTH; 319 | mouse_y = HIWORD(lParam) * SCREEN_HEIGHT / WINDOW_HEIGHT; 320 | mdown = 1; 321 | mrelease = 0; 322 | mclick = 1; 323 | } break; 324 | 325 | case WM_LBUTTONUP: 326 | { 327 | mdown = 0; 328 | mrelease = 1; 329 | } break; 330 | case WM_MOUSEMOVE: 331 | { 332 | mouse_x = LOWORD(lParam) * SCREEN_WIDTH / WINDOW_WIDTH; 333 | mouse_y = HIWORD(lParam) * SCREEN_HEIGHT / WINDOW_HEIGHT; 334 | mmove = 1; 335 | } break; 336 | case WM_PAINT: 337 | { 338 | if(!screen) break; 339 | PAINTSTRUCT ps; 340 | HDC hdc = BeginPaint( hwnd, &ps ); 341 | 342 | int cx = 0, cy = 0; 343 | if(cursor) { 344 | cx = mouse_x - cursor_hsx; 345 | cy = mouse_y - cursor_hsy; 346 | int cw = bm_width(cursor); 347 | int ch = bm_height(cursor); 348 | 349 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch); 350 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch); 351 | } 352 | 353 | #if EPX_SCALE 354 | scale_epx_i(screen, epx); 355 | #endif 356 | 357 | #if WINDOW_WIDTH == VSCREEN_WIDTH && WINDOW_HEIGHT == VSCREEN_HEIGHT 358 | BitBlt( hdc, 0, 0, VSCREEN_WIDTH, VSCREEN_HEIGHT, hdcMem, 0, 0, SRCCOPY ); 359 | #else 360 | StretchBlt(hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, hdcMem, 0, 0, VSCREEN_WIDTH, VSCREEN_HEIGHT, SRCCOPY); 361 | #endif 362 | EndPaint( hwnd, &ps ); 363 | 364 | if(cursor) { 365 | bm_maskedblit(screen, cx, cy, cursor_back, 0, 0, bm_width(cursor_back), bm_height(cursor_back)); 366 | } 367 | 368 | break; 369 | } 370 | /* Don't erase the background - it causes flickering 371 | http://stackoverflow.com/a/14153470/115589 */ 372 | case WM_ERASEBKGND: 373 | return 1; 374 | 375 | default: 376 | return DefWindowProc(hwnd, message, wParam, lParam); 377 | } 378 | return 0; 379 | } 380 | 381 | int APIENTRY WinMain( 382 | HINSTANCE hInstance, 383 | HINSTANCE hPrevInstance, 384 | LPSTR lpCmdLine, 385 | int nCmdShow 386 | ) 387 | { 388 | MSG msg; 389 | WNDCLASS wc; 390 | HWND hwnd; 391 | double elapsedSeconds = 0.0; 392 | 393 | #ifdef _MSC_VER 394 | errno_t err = fopen_s(&logfile, LOG_FILE_NAME, "w"); 395 | if (err != 0) { 396 | exit_error("Unable to open log file `%s`"); 397 | } 398 | #elif defined USE_LOG_STREAM 399 | logfile = LOG_STREAM; 400 | #else 401 | logfile = fopen(LOG_FILE_NAME, "w"); 402 | if(!logfile) { 403 | exit_error("Unable to open log file `%s`"); 404 | } 405 | #endif 406 | 407 | rlog("%s","GDI Framework: Application Running"); 408 | 409 | ZeroMemory(&wc, sizeof wc); 410 | wc.hInstance = hInstance; 411 | wc.lpszClassName = szAppName; 412 | wc.lpfnWndProc = (WNDPROC)WndProc; 413 | wc.style = CS_DBLCLKS|CS_VREDRAW|CS_HREDRAW; 414 | wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 415 | wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 416 | wc.hCursor = LoadCursor(NULL, IDC_ARROW); 417 | 418 | if (FALSE == RegisterClass(&wc)) 419 | return 0; 420 | 421 | hwnd = CreateWindow( 422 | szAppName, 423 | szTitle, 424 | //WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 425 | //WS_POPUP, // For no border/titlebar etc 426 | WS_CAPTION, 427 | CW_USEDEFAULT, 428 | CW_USEDEFAULT, 429 | WINDOW_WIDTH, 430 | WINDOW_HEIGHT, 431 | 0, 432 | 0, 433 | hInstance, 434 | 0); 435 | 436 | if (NULL == hwnd) 437 | return 0; 438 | 439 | FitWindow(hwnd); 440 | 441 | ShowWindow(hwnd , SW_SHOW); 442 | 443 | /* Todo: I didn't bother with higher resolution timers: 444 | https://msdn.microsoft.com/en-us/library/dn553408(v=vs.85).aspx */ 445 | 446 | rlog("%s","GDI Framework: Entering main loop"); 447 | quit = 0; 448 | for(;;) { 449 | clock_t startTime, endTime; 450 | startTime = clock(); 451 | while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) { 452 | TranslateMessage(&msg); 453 | DispatchMessage(&msg); 454 | } 455 | if(quit) break; 456 | 457 | Sleep(1); 458 | if(elapsedSeconds > 1.0/FPS) { 459 | 460 | if(!render(elapsedSeconds)) { 461 | DestroyWindow(hwnd); 462 | } else { 463 | if(show_fps && n_elapsed > 0) { 464 | double sum = 0; 465 | int i, n = n_elapsed > 0xFF ? 0xFF : n_elapsed; 466 | for(i = 0; i < n; i++) sum += frameTimes[i]; 467 | double avg = sum / n; 468 | double fps = 1.0 / avg; 469 | BmFont * f = bm_get_font(screen); 470 | bm_reset_font(screen); 471 | bm_set_color(screen, bm_atoi("red")); 472 | bm_fillrect(screen, 0, 0, 50, 10); 473 | bm_set_color(screen, bm_atoi("yellow")); 474 | bm_printf(screen, 1, 1, "%3.2f", fps); 475 | bm_set_font(screen, f); 476 | } 477 | frameTimes[(n_elapsed++) & 0xFF] = elapsedSeconds; 478 | } 479 | 480 | InvalidateRect(hwnd, 0, TRUE); 481 | elapsedSeconds = 0.0; 482 | pressed_key = 0; 483 | mrelease = 0; 484 | mmove = 0; 485 | mclick = 0; 486 | } 487 | endTime = clock(); 488 | elapsedSeconds += (double)(endTime - startTime) / CLOCKS_PER_SEC; 489 | } 490 | rlog("%s","GDI Framework: Main loop stopped"); 491 | rlog("%s","Application Done!\n"); 492 | 493 | #if !USE_LOG_STREAM 494 | fclose(logfile); 495 | #endif 496 | 497 | return msg.wParam; 498 | } 499 | 500 | /* Make sure the client area fits; center the window in the process */ 501 | static void FitWindow(HWND hwnd) { 502 | RECT rcClient, rwClient; 503 | POINT ptDiff; 504 | HWND hwndParent; 505 | RECT rcParent, rwParent; 506 | POINT ptPos; 507 | 508 | GetClientRect(hwnd, &rcClient); 509 | GetWindowRect(hwnd, &rwClient); 510 | ptDiff.x = (rwClient.right - rwClient.left) - rcClient.right; 511 | ptDiff.y = (rwClient.bottom - rwClient.top) - rcClient.bottom; 512 | 513 | hwndParent = GetParent(hwnd); 514 | if (NULL == hwndParent) 515 | hwndParent = GetDesktopWindow(); 516 | 517 | GetWindowRect(hwndParent, &rwParent); 518 | GetClientRect(hwndParent, &rcParent); 519 | 520 | ptPos.x = rwParent.left + (rcParent.right - WINDOW_WIDTH) / 2; 521 | ptPos.y = rwParent.top + (rcParent.bottom - WINDOW_HEIGHT) / 2; 522 | 523 | MoveWindow(hwnd, ptPos.x, ptPos.y, WINDOW_WIDTH + ptDiff.x, WINDOW_HEIGHT + ptDiff.y, 0); 524 | } 525 | 526 | /* 527 | Alternative to CommandLineToArgvW(). 528 | I used a compiler where shellapi.h was not available, 529 | so this function breaks it down according to the last set of rules in 530 | http://i1.blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx 531 | 532 | Only a long time after I wrote this did I discover that you can actually 533 | use __argc, __argv to access the commandline parameters... 534 | 535 | extern "C" int __stdcall WinMain( struct HINSTANCE__*, struct HINSTANCE__*, char*, int ) { 536 | return main( __argc, __argv ); 537 | } 538 | 539 | http://www.testdeveloper.com/2010/03/16/a-few-ways-to-access-argc-and-argv-in-c/ 540 | 541 | */ 542 | static int split_cmd_line(char *cmdl, char *argv[], int max) { 543 | 544 | int argc = 0; 545 | char *p = cmdl, *q = p, *arg = p; 546 | int state = 1; 547 | while(state) { 548 | switch(state) { 549 | case 1: 550 | if(argc == max) return argc; 551 | if(!*p) { 552 | state = 0; 553 | } else if(isspace(*p)) { 554 | *q++ = *p++; 555 | } else if(*p == '\"') { 556 | state = 2; 557 | *q++ = *p++; 558 | arg = q; 559 | } else { 560 | state = 3; 561 | arg = q; 562 | *q++ = *p++; 563 | } 564 | break; 565 | case 2: 566 | if(!*p) { 567 | argv[argc++] = arg; 568 | *q++ = '\0'; 569 | state = 0; 570 | } else if(*p == '\"') { 571 | if(p[1] == '\"') { 572 | state = 2; 573 | *q++ = *p; 574 | p+=2; 575 | } else { 576 | state = 1; 577 | argv[argc++] = arg; 578 | *q++ = '\0'; 579 | p++; 580 | } 581 | } else { 582 | *q++ = *p++; 583 | } 584 | break; 585 | case 3: 586 | if(!*p) { 587 | state = 0; 588 | argv[argc++] = arg; 589 | *q++ = '\0'; 590 | } else if(isspace(*p)) { 591 | state = 1; 592 | argv[argc++] = arg; 593 | *q++ = '\0'; 594 | p++; 595 | } else { 596 | *q++ = *p++; 597 | } 598 | break; 599 | } 600 | } 601 | return argc; 602 | } 603 | 604 | /* EPX 2x scaling */ 605 | #if EPX_SCALE 606 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out) { 607 | int x, y, mx = in->w, my = in->h; 608 | if(!out) return NULL; 609 | if(!in) return out; 610 | if(out->w < (mx << 1)) mx = (out->w - 1) >> 1; 611 | if(out->h < (my << 1)) my = (out->h - 1) >> 1; 612 | for(y = 0; y < my; y++) { 613 | for(x = 0; x < mx; x++) { 614 | unsigned int P = bm_get(in, x, y); 615 | unsigned int A = (y > 0) ? bm_get(in, x, y - 1) : P; 616 | unsigned int B = (x < in->w - 1) ? bm_get(in, x + 1, y) : P; 617 | unsigned int C = (x > 0) ? bm_get(in, x - 1, y) : P; 618 | unsigned int D = (y < in->h - 1) ? bm_get(in, x, y + 1) : P; 619 | 620 | unsigned int P1 = P, P2 = P, P3 = P, P4 = P; 621 | 622 | if(C == A && C != D && A != B) P1 = A; 623 | if(A == B && A != C && B != D) P2 = B; 624 | if(B == D && B != A && D != C) P4 = D; 625 | if(D == C && D != B && C != A) P3 = C; 626 | 627 | bm_set(out, (x << 1), (y << 1), P1); 628 | bm_set(out, (x << 1) + 1, (y << 1), P2); 629 | bm_set(out, (x << 1), (y << 1) + 1, P3); 630 | bm_set(out, (x << 1) + 1, (y << 1) + 1, P4); 631 | } 632 | } 633 | return out; 634 | } 635 | #endif 636 | 637 | char *readfile(const char *fname) { 638 | FILE *f; 639 | size_t len, r; 640 | char *bytes; 641 | 642 | #ifdef _MSC_VER 643 | errno_t err = fopen_s(&f, fname, "rb"); 644 | if(err != 0) 645 | return NULL; 646 | #else 647 | f = fopen(fname, "rb"); 648 | if (!f) 649 | return NULL; 650 | #endif 651 | 652 | fseek(f, 0, SEEK_END); 653 | len = ftell(f); 654 | rewind(f); 655 | 656 | if(!(bytes = malloc(len+2))) 657 | return NULL; 658 | r = fread(bytes, 1, len, f); 659 | 660 | if(r != len) { 661 | free(bytes); 662 | return NULL; 663 | } 664 | 665 | fclose(f); 666 | bytes[len] = '\0'; 667 | 668 | return bytes; 669 | } 670 | 671 | Bitmap *get_bmp(const char *filename) { 672 | return bm_load(filename); 673 | } 674 | -------------------------------------------------------------------------------- /d.awk: -------------------------------------------------------------------------------- 1 | #! /usr/bin/awk -f 2 | 3 | ## 4 | # d.awk 5 | # ===== 6 | # 7 | # Converts Markdown in C/C++-style code comments to HTML. \ 8 | # 9 | # 10 | # The comments must have the `/** */` pattern. Every line in the comment 11 | # must start with a *. Like so: 12 | # 13 | # ```c 14 | # /** 15 | # * Markdown here... 16 | # */ 17 | # ``` 18 | # 19 | # Alternatively, three slashes can also be used: `/// Markdown here` 20 | # 21 | # ## Configuration Options 22 | # 23 | # You can set these in the BEGIN block below, or pass them to the script through the 24 | # `-v` command-line option: 25 | # 26 | # - `-vTitle="My Document Title"` to set the `` in the `<head/>` section of the HTML 27 | # - `-vStyleSheet=style.css` to use a separate CSS file as style sheet. 28 | # - `-vCss=n` with n either 0 to disable CSS or 1 to enable; Default = 1 29 | # - `-vTopLinks=1` to have links to the top of the doc next to headers. 30 | # - `-vMaxWidth=1080px` specifies the Maximum width of the HTML output. 31 | # - `-vPretty=0` disable syntax highlighting with Google's [code prettify][]. 32 | # - `-vMermaid=0` disable [Mermaid][] diagrams. 33 | # - `-vMathjax=0` disable [MathJax][] mathematical rendering. 34 | # - `-vHideToCLevel=n` specifies the level of the ToC that should be collapsed by default. 35 | # - `-vLang=n` specifies the value of the `lang` attribute of the <html> tag; Default = "en" 36 | # - `-vTables=0` disables support for [GitHub-style tables][github-tables] 37 | # - `-vclassic_underscore=1` words_with_underscores behave like old markdown where the 38 | # underscores in the word counts as emphasis. The default behaviour is to have 39 | # `words_like_this` not contain any emphasis. 40 | # - `-vNumberHeadings=1` to enable or disable section numbers in front of headings; Default = 1 41 | # - `-vNumberH1s=1`: if `NumberHeadings` is enabled, `<H1>` headings are not numbered by 42 | # default (because the `<H1>` would typically contain the document title). Use this to 43 | # number `<H1>`s as well. 44 | # - `-vClean=1` to treat the input file as a plain Markdown file. 45 | # You could use `./d.awk -vClean=1 README.md > README.html` to generate HTML from 46 | # your README, for example. 47 | # 48 | # I've tested it with Gawk, Nawk and Mawk. 49 | # Gawk and Nawk worked without issues, but don't use the `-W compat` 50 | # or `-W traditional` settings with Gawk. 51 | # Mawk v1.3.4 worked correctly but v1.3.3 choked on it. 52 | # 53 | # [code prettify]: https://github.com/google/code-prettify 54 | # [Mermaid]: https://github.com/mermaid-js/mermaid 55 | # [MathJax]: https://www.mathjax.org/ 56 | # [github-tables]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#tables 57 | # [github-mermaid]: https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/ 58 | # [github-math]: https://github.blog/changelog/2022-05-19-render-mathematical-expressions-in-markdown/ 59 | # 60 | # ## Extensions 61 | # 62 | # - Insert a Table of Contents by using `\![toc]`. 63 | # The Table of Contents is collapsed by default: 64 | # - Use `\\![toc+]` to insert a ToC that is expanded by default; 65 | # - Use `\\![toc-]` for a collapsed ToC. 66 | # - Github-style ```` ``` ```` code blocks supported. 67 | # - Github-style `~~strikethrough~~` supported. 68 | # - [Github-style tables][github-tables] are supported. 69 | # - [GitHub-style Mermaid diagrams][github-mermaid] 70 | # - [GitHub-style mathematical expressions][github-math]: $\sqrt{3x-1}+(1+x)^2$ 71 | # - GitHub-style task lists `- [x]` are supported for documenting bugs and todo lists in code. 72 | # - The `id` attribute of anchor tags `<a>` are treated as in GitHub: 73 | # The tag's id should be the title, in lower case stripped of non-alphanumeric characters 74 | # (except hyphens and spaces) and then with all spaces replaced with hyphens. 75 | # then add -1, -2, -3 until it's unique 76 | # See [here](https://gist.github.com/asabaylus/3071099) (especially the comment by TomOnTime) 77 | # and [here](https://gist.github.com/rachelhyman/b1f109155c9dafffe618) 78 | # - A couple of ideas from MultiMarkdown: 79 | # - `\\[^footnotes]` are supported. 80 | # - `*[abbr]:` Abbreviations are supported. 81 | # - Space followed by \\ at the end of a line also forces a line break. 82 | # - Default behaviour is to have words_like_this not contain emphasis. 83 | # 84 | # Limitations: 85 | # 86 | # - You can't nest `<blockquote>`s, and they can't contain nested lists 87 | # or `pre` blocks. You can work around this by using HTML directly. 88 | # - It takes some liberties with how inline (particularly block-level) HTML is processed and not 89 | # all HTML tags supported. HTML forms and `<script/>` tags are out. 90 | # - Paragraphs in lists differ a bit from other markdowns. Use indented blank lines to get 91 | # to insert `<br>` tags to emulate paragraphs. Blank lines stop the list by inserting the 92 | # `</ul>` or `</ol>` tags. 93 | # 94 | # ## References 95 | # 96 | # - <https://tools.ietf.org/html/rfc7764> 97 | # - <http://daringfireball.net/projects/markdown/syntax> 98 | # - <https://guides.github.com/features/mastering-markdown/> 99 | # - <http://fletcher.github.io/MultiMarkdown-4/syntax> 100 | # - <http://spec.commonmark.org> 101 | # 102 | # ## License 103 | # 104 | # (c) 2016-2023 Werner Stoop 105 | # Copying and distribution of this file, with or without modification, 106 | # are permitted in any medium without royalty provided the copyright 107 | # notice and this notice are preserved. This file is offered as-is, 108 | # without any warranty. 109 | # 110 | 111 | BEGIN { 112 | 113 | # Configuration options 114 | if(Title== "") Title = "Documentation"; 115 | if(Css== "") Css = 1; 116 | 117 | if(Pretty== "") Pretty = 1; 118 | if(Mermaid== "") Mermaid = 1; 119 | if(Mathjax=="") Mathjax = 1; 120 | 121 | if(HideToCLevel== "") HideToCLevel = 3; 122 | if(Lang == "") Lang = "en"; 123 | if(Tables == "") Tables = 1; 124 | #TopLinks = 1; 125 | #classic_underscore = 1; 126 | if(MaxWidth=="") MaxWidth="1080px"; 127 | if(NumberHeadings=="") NumberHeadings = 1; 128 | if(NumberH1s=="") NumberH1s = 0; 129 | 130 | Mode = (Clean)?"p":"none"; 131 | ToC = ""; ToCLevel = 1; 132 | CSS = init_css(Css); 133 | for(i = 0; i < 128; i++) 134 | _ord[sprintf("%c", i)] = i; 135 | srand(); 136 | } 137 | 138 | !Clean && !Multi && /\/\*\*/ { 139 | Mode = "p"; 140 | sub(/^.*\/\*\*/,""); 141 | if(match($0,/\*\//)) { 142 | sub(/\*\/.*/,""); 143 | Out = Out filter($0); 144 | Out = Out tag(Mode, scrub(Buf)); 145 | Buf = ""; 146 | Prev = ""; 147 | } else { 148 | Out = Out filter($0); 149 | Multi = 1; 150 | } 151 | } 152 | 153 | Multi && /\*\// { 154 | gsub(/\*\/.*$/,""); 155 | if(match($0, /^[[:space:]]*\*/)) 156 | Out = Out filter(substr($0, RSTART+RLENGTH)); 157 | if(Mode == "ul" || Mode == "ol") { 158 | while(ListLevel > 1) 159 | Buf = Buf "\n</" Open[ListLevel--] ">"; 160 | Out = Out tag(Mode, Buf "\n"); 161 | } else if(Mode == "table") { 162 | Out = Out end_table(); 163 | } else { 164 | Buf = trim(scrub(Buf)); 165 | if(Buf) 166 | Out = Out tag(Mode, Buf); 167 | } 168 | Mode = "none"; 169 | Multi = 0; 170 | Buf = ""; 171 | Prev = ""; 172 | } 173 | Multi { 174 | gsub(/\r/, "", $0); 175 | if(match($0,/[[:graph:]]/) && substr($0,RSTART,1)!="*") 176 | next; 177 | gsub(/^[[:space:]]*\*/, "", $0); 178 | } 179 | Multi { Out = Out filter($0); } 180 | 181 | # These are the rules for `///` single-line comments: 182 | Single && $0 !~ /\/\/\// { 183 | if(Mode == "ul" || Mode == "ol") { 184 | while(ListLevel > 1) 185 | Buf = Buf "\n</" Open[ListLevel--] ">"; 186 | Out = Out tag(Mode, Buf "\n"); 187 | } else { 188 | Buf = trim(scrub(Buf)); 189 | if(Buf) 190 | Out = Out tag(Mode, Buf); 191 | } 192 | Mode = "none"; 193 | Single = 0; 194 | Buf = ""; 195 | Prev = ""; 196 | } 197 | Single && /\/\/\// { 198 | sub(/.*\/\/\//,""); 199 | Out = Out filter($0); 200 | } 201 | !Clean && !Single && !Multi && /\/\/\// { 202 | sub(/.*\/\/\//,""); 203 | Single = 1; 204 | Mode = "p"; 205 | Out = Out filter($0); 206 | } 207 | 208 | Clean { 209 | Out = Out filter($0); 210 | } 211 | 212 | END { 213 | 214 | if(Mode == "ul" || Mode == "ol") { 215 | while(ListLevel > 1) 216 | Buf = Buf "\n</" Open[ListLevel--] ">"; 217 | Out = Out tag(Mode, Buf "\n"); 218 | } else if(Mode == "pre") { 219 | while(ListLevel > 1) 220 | Buf = Buf "\n</" Open[ListLevel--] ">"; 221 | Out = Out tag(Mode, Buf "\n"); 222 | } else if(Mode == "table") { 223 | Out = Out end_table(); 224 | } else { 225 | Buf = trim(scrub(Buf)); 226 | if(Buf) 227 | Out = Out tag(Mode, Buf); 228 | } 229 | 230 | print "<!DOCTYPE html>\n<html lang=\"" Lang "\"><head>" 231 | print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"; 232 | print "<title>" Title ""; 233 | if(StyleSheet) 234 | print ""; 235 | else { 236 | if(Pretty && HasPretty) { 237 | CSS = CSS "\nbody {--str:#a636d8;--kwd:#4646ff;--com:#56a656;--lit:#e05e10;--typ:#0222ce;--pun:#595959;}\n"\ 238 | "body.dark-theme {--str:#eb28df;--kwd:#f7d689;--com:#267b26;--lit: #ff8181;--typ:#228dff;--pun: #EEE;}\n"\ 239 | "@media (prefers-color-scheme: dark) {\n"\ 240 | " body.light-theme {--str:#a636d8;--kwd:#4646ff;--com:#56a656;--lit:#e05e10;--typ:#0222ce;--pun:#595959;}\n"\ 241 | " body {--str:#eb28df;--kwd:#f7d689;--com:#267b26;--lit: #ff8181;--typ:#228dff;--pun: #EEE;}\n"\ 242 | "}\n"\ 243 | ".com { color:var(--com); } /* comment */\n"\ 244 | ".kwd, .tag { color:var(--kwd); } /* keyword, markup tag */\n"\ 245 | ".typ, .atn { color:var(--typ); } /* type name, html/xml attribute name */\n"\ 246 | ".str, .atv { color:var(--str); } /* string literal, html/xml attribute value */\n"\ 247 | ".lit, .dec, .var { color:var(--lit); } /* literal */\n"\ 248 | ".pun, .opn, .clo { color:var(--pun); } /* punctuation */\n"\ 249 | ".pln { color:var(--alt-color); } /* plain text */\n"\ 250 | "@media print, projection {\n"\ 251 | " .com { font-style: italic }\n"\ 252 | " .kwd, .typ, .tag { font-weight: bold }\n"\ 253 | "}"; 254 | } 255 | print ""; 259 | } 260 | if(ToC && match(Out, /!\[toc[-+]?\]/)) 261 | print ""; 277 | print ""; 278 | 279 | print "\n" \ 280 | "\n" \ 281 | "\n" \ 282 | "\n" \ 283 | "\n" \ 284 | "\n Toggle Dark Mode\n"; 285 | print ""; 291 | 292 | if(Out) { 293 | Out = fix_footnotes(Out); 294 | Out = fix_links(Out); 295 | Out = fix_abbrs(Out); 296 | Out = make_toc(Out); 297 | 298 | print trim(Out); 299 | if(footnotes) { 300 | footnotes = fix_links(footnotes); 301 | print "
    \n" footnotes "
"; 302 | } 303 | } 304 | 305 | if(Pretty && HasPretty) { 306 | print ""; 307 | } 308 | if(Mermaid && HasMermaid) { 309 | print ""; 310 | print ""; 311 | } 312 | if(Mathjax && HasMathjax) { 313 | print ""; 314 | print ""; 315 | } 316 | print "" 317 | } 318 | 319 | function escape(st) { 320 | gsub(/&/, "\\&", st); 321 | gsub(//, "\\>", st); 323 | return st; 324 | } 325 | function strip_tags(st) { 326 | gsub(/<\/?[^>]+>/,"",st); 327 | return st; 328 | } 329 | function trim(st) { 330 | sub(/^[[:space:]]+/, "", st); 331 | sub(/[[:space:]]+$/, "", st); 332 | return st; 333 | } 334 | function filter(st, res,tmp, linkdesc, url, delim, edelim, name, def, plang, mmaid, cols, i) { 335 | if(Mode == "p") { 336 | if(match(st, /^[[:space:]]*\[[-._[:alnum:][:space:]]+\]:/)) { 337 | linkdesc = ""; LastLink = 0; 338 | match(st,/\[.*\]/); 339 | LinkRef = tolower(substr(st, RSTART+1, RLENGTH-2)); 340 | st = substr(st, RSTART+RLENGTH+2); 341 | match(st, /[^[:space:]]+/); 342 | url = substr(st, RSTART, RLENGTH); 343 | st = substr(st, RSTART+RLENGTH+1); 344 | if(match(url, /^<.*>/)) 345 | url = substr(url, RSTART+1, RLENGTH-2); 346 | if(match(st, /["'(]/)) { 347 | delim = substr(st, RSTART, 1); 348 | edelim = (delim == "(") ? ")" : delim; 349 | if(match(st, delim ".*" edelim)) 350 | linkdesc = substr(st, RSTART+1, RLENGTH-2); 351 | } 352 | LinkUrls[LinkRef] = escape(url); 353 | if(!linkdesc) LastLink = 1; 354 | LinkDescs[LinkRef] = escape(linkdesc); 355 | return; 356 | } else if(LastLink && match(st, /^[[:space:]]*["'(]/)) { 357 | match(st, /["'(]/); 358 | delim = substr(st, RSTART, 1); 359 | edelim = (delim == "(") ? ")" : delim; 360 | st = substr(st, RSTART); 361 | if(match(st, delim ".*" edelim)) 362 | LinkDescs[LinkRef] = escape(substr(st,RSTART+1,RLENGTH-2)); 363 | LastLink = 0; 364 | return; 365 | } else if(match(st, /^[[:space:]]*\[\^[-._[:alnum:][:space:]]+\]:[[:space:]]*/)) { 366 | match(st, /\[\^[[:alnum:]]+\]:/); 367 | name = substr(st, RSTART+2,RLENGTH-4); 368 | def = substr(st, RSTART+RLENGTH+1); 369 | Footnote[tolower(name)] = scrub(def); 370 | return; 371 | } else if(match(st, /^[[:space:]]*\*\[[[:alnum:]]+\]:[[:space:]]*/)) { 372 | match(st, /\[[[:alnum:]]+\]/); 373 | name = substr(st, RSTART+1,RLENGTH-2); 374 | def = substr(st, RSTART+RLENGTH+2); 375 | Abbrs[toupper(name)] = def; 376 | return; 377 | } else if(match(st, /^(( )| *\t)/) || match(st, /^[[:space:]]*```+[[:alnum:]]*/)) { 378 | Preterm = trim(substr(st, RSTART,RLENGTH)); 379 | st = substr(st, RSTART+RLENGTH); 380 | if(Buf) res = tag("p", scrub(Buf)); 381 | Buf = st; 382 | push("pre"); 383 | } else if(!trim(Prev) && match(st, /^[[:space:]]*[*-][[:space:]]*[*-][[:space:]]*[*-][-*[:space:]]*$/)) { 384 | if(Buf) res = tag("p", scrub(Buf)); 385 | Buf = ""; 386 | res = res "
\n"; 387 | } else if(match(st, /^[[:space:]]*===+[[:space:]]*$/)) { 388 | Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)); 389 | if(Buf) res= tag("p", scrub(Buf)); 390 | if(Prev) res = res heading(1, scrub(Prev)); 391 | Buf = ""; 392 | } else if(match(st, /^[[:space:]]*---+[[:space:]]*$/)) { 393 | Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)); 394 | if(Buf) res = tag("p", scrub(Buf)); 395 | if(Prev) res = res heading(2, scrub(Prev)); 396 | Buf = ""; 397 | } else if(match(st, /^[[:space:]]*#+/)) { 398 | sub(/#+[[:space:]]*$/, "", st); 399 | match(st, /#+/); 400 | ListLevel = RLENGTH; 401 | tmp = substr(st, RSTART+RLENGTH); 402 | if(Buf) res = tag("p", scrub(Buf)); 403 | res = res heading(ListLevel, scrub(trim(tmp))); 404 | Buf = ""; 405 | } else if(match(st, /^[[:space:]]*>/)) { 406 | if(Buf) res = tag("p", scrub(Buf)); 407 | Buf = scrub(trim(substr(st, RSTART+RLENGTH))); 408 | push("blockquote"); 409 | } else if(Tables && match(st, /.*\|(.*\|)+/)) { 410 | if(Buf) res = tag("p", scrub(Buf)); 411 | Row = 1; 412 | for(i = 1; i <= MaxCols; i++) 413 | Align[i] = ""; 414 | process_table_row(st); 415 | push("table"); 416 | } else if(match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)[[:space:]]/)) { 417 | if(Buf) res = tag("p", scrub(Buf)); 418 | Buf=""; 419 | match(st, /^[[:space:]]*/); 420 | ListLevel = 1; 421 | indent[ListLevel] = RLENGTH; 422 | Open[ListLevel]=match(st, /^[[:space:]]*[*+-][[:space:]]*/)?"ul":"ol"; 423 | push(Open[ListLevel]); 424 | res = res filter(st); 425 | } else if(match(st, /^[[:space:]]*$/)) { 426 | if(trim(Buf)) { 427 | res = tag("p", scrub(trim(Buf))); 428 | Buf = ""; 429 | } 430 | } else 431 | Buf = Buf st "\n"; 432 | LastLink = 0; 433 | } else if(Mode == "blockquote") { 434 | if(match(st, /^[[:space:]]*>[[:space:]]*$/)) 435 | Buf = Buf "\n

"; 436 | else if(match(st, /^[[:space:]]*>/)) 437 | Buf = Buf "\n" scrub(trim(substr(st, RSTART+RLENGTH))); 438 | else if(match(st, /^[[:space:]]*$/)) { 439 | res = tag("blockquote", tag("p", trim(Buf))); 440 | pop(); 441 | res = res filter(st); 442 | } else 443 | Buf = Buf st; 444 | } else if(Mode == "table") { 445 | if(match(st, /.*\|(.*\|)+/)) { 446 | process_table_row(st); 447 | } else { 448 | res = end_table(); 449 | pop(); 450 | res = res filter(st); 451 | } 452 | } else if(Mode == "pre") { 453 | if(!Preterm && match(st, /^(( )| *\t)/) || Preterm && !match(st, /^[[:space:]]*```+/)) 454 | Buf = Buf ((Buf)?"\n":"") substr(st, RSTART+RLENGTH); 455 | else { 456 | gsub(/\t/," ",Buf); 457 | if(length(trim(Buf)) > 0) { 458 | plang = ""; mmaid=0; 459 | if(match(Preterm, /^[[:space:]]*```+/)) { 460 | plang = trim(substr(Preterm, RSTART+RLENGTH)); 461 | if(plang) { 462 | if(plang == "mermaid") { 463 | mmaid = 1; 464 | HasMermaid = 1; 465 | } else { 466 | HasPretty = 1; 467 | if(plang == "auto") 468 | plang = "class=\"prettyprint\""; 469 | else 470 | plang = "class=\"prettyprint lang-" plang "\""; 471 | } 472 | } 473 | } 474 | if(mmaid && Mermaid) 475 | res = tag("div", Buf, "class=\"mermaid\""); 476 | else 477 | res = tag("pre", tag("code", escape(Buf), plang)); 478 | } 479 | pop(); 480 | if(Preterm) sub(/^[[:space:]]*```+[[:alnum:]]*/,"",st); 481 | res = res filter(st); 482 | } 483 | } else if(Mode == "ul" || Mode == "ol") { 484 | if(ListLevel == 0 || match(st, /^[[:space:]]*$/) && (RLENGTH <= indent[1])) { 485 | while(ListLevel > 1) 486 | Buf = Buf "\n"; 487 | res = tag(Mode, "\n" Buf "\n"); 488 | pop(); 489 | } else { 490 | if(match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)/)) { 491 | tmp = substr(st, RLENGTH+1); 492 | match(st, /^[[:space:]]*/); 493 | if(RLENGTH > indent[ListLevel]) { 494 | indent[++ListLevel] = RLENGTH; 495 | if(match(st, /^[[:space:]]*[*+-]/)) 496 | Open[ListLevel] = "ul"; 497 | else 498 | Open[ListLevel] = "ol"; 499 | Buf = Buf "\n<" Open[ListLevel] ">"; 500 | } else while(RLENGTH < indent[ListLevel]) 501 | Buf = Buf "\n"; 502 | if(match(tmp,/^[[:space:]]*\[[xX[:space:]]\]/)) { 503 | st = substr(tmp,RLENGTH+1); 504 | tmp = tolower(substr(tmp,RSTART,RLENGTH)); 505 | Buf = Buf "

  • " scrub(st); 506 | } else 507 | Buf = Buf "
  • " scrub(tmp); 508 | } else if(match(st, /^[[:space:]]*$/)){ 509 | Buf = Buf "
    \n"; 510 | } else { 511 | sub(/^[[:space:]]+/,"",st); 512 | Buf = Buf "\n" scrub(st); 513 | } 514 | } 515 | } 516 | Prev = st; 517 | return res; 518 | } 519 | function scrub(st, mp, ms, me, r, p, tg, a, tok) { 520 | sub(/ $/,"
    \n",st); 521 | gsub(/( |[[:space:]]+\\)\n/,"
    \n",st); 522 | gsub(/( |[[:space:]]+\\)$/,"
    \n",st); 523 | while(match(st, /(__?|\*\*?|~~|`+|\$+|\\\(|[&><\\])/)) { 524 | a = substr(st, 1, RSTART-1); 525 | mp = substr(st, RSTART, RLENGTH); 526 | ms = substr(st, RSTART-1,1); 527 | me = substr(st, RSTART+RLENGTH, 1); 528 | p = RSTART+RLENGTH; 529 | 530 | if(!classic_underscore && match(mp,/_+/)) { 531 | if(match(ms,/[[:alnum:]]/) && match(me,/[[:alnum:]]/)) { 532 | tg = substr(st, 1, index(st, mp)); 533 | r = r tg; 534 | st = substr(st, index(st, mp) + 1); 535 | continue; 536 | } 537 | } 538 | st = substr(st, p); 539 | r = r a; 540 | ms = ""; 541 | 542 | if(mp == "\\") { 543 | if(match(st, /^!?\[/)) { 544 | r = r "\\" substr(st, RSTART, RLENGTH); 545 | st = substr(st, 2); 546 | } else if(match(st, /^(\*\*|__|~~|`+)/)) { 547 | r = r substr(st, 1, RLENGTH); 548 | st = substr(st, RLENGTH+1); 549 | } else { 550 | r = r substr(st, 1, 1); 551 | st = substr(st, 2); 552 | } 553 | continue; 554 | } else if(mp == "_" || mp == "*") { 555 | if(match(me,/[[:space:]]/)) { 556 | r = r mp; 557 | continue; 558 | } 559 | p = index(st, mp); 560 | while(p && match(substr(st, p-1, 1),/[\\[:space:]]/)) { 561 | ms = ms substr(st, 1, p-1) mp; 562 | st = substr(st, p + length(mp)); 563 | p = index(st, mp); 564 | } 565 | if(!p) { 566 | r = r mp ms; 567 | continue; 568 | } 569 | ms = ms substr(st,1,p-1); 570 | r = r itag("em", scrub(ms)); 571 | st = substr(st,p+length(mp)); 572 | } else if(mp == "__" || mp == "**") { 573 | if(match(me,/[[:space:]]/)) { 574 | r = r mp; 575 | continue; 576 | } 577 | p = index(st, mp); 578 | while(p && match(substr(st, p-1, 1),/[\\[:space:]]/)) { 579 | ms = ms substr(st, 1, p-1) mp; 580 | st = substr(st, p + length(mp)); 581 | p = index(st, mp); 582 | } 583 | if(!p) { 584 | r = r mp ms; 585 | continue; 586 | } 587 | ms = ms substr(st,1,p-1); 588 | r = r itag("strong", scrub(ms)); 589 | st = substr(st,p+length(mp)); 590 | } else if(mp == "~~") { 591 | p = index(st, mp); 592 | if(!p) { 593 | r = r mp; 594 | continue; 595 | } 596 | while(p && substr(st, p-1, 1) == "\\") { 597 | ms = ms substr(st, 1, p-1) mp; 598 | st = substr(st, p + length(mp)); 599 | p = index(st, mp); 600 | } 601 | ms = ms substr(st,1,p-1); 602 | r = r itag("del", scrub(ms)); 603 | st = substr(st,p+length(mp)); 604 | } else if(match(mp, /`+/)) { 605 | p = index(st, mp); 606 | if(!p) { 607 | r = r mp; 608 | continue; 609 | } 610 | ms = substr(st,1,p-1); 611 | r = r itag("code", escape(ms)); 612 | st = substr(st,p+length(mp)); 613 | } else if(Mathjax && match(mp, /\$+/)) { 614 | tok = substr(mp, RSTART, RLENGTH); 615 | p = index(st, mp); 616 | if(!p) { 617 | r = r mp; 618 | continue; 619 | } 620 | ms = substr(st,1,p-1); 621 | r = r tok escape(ms) tok; 622 | st = substr(st,p+length(mp)); 623 | HasMathjax = 1; 624 | } else if(Mathjax && mp=="\\(") { 625 | p = index(st, "\\)"); 626 | if(!p) { 627 | r = r mp; 628 | continue; 629 | } 630 | ms = substr(st,1,p-1); 631 | r = r "\\(" escape(ms) "\\)"; 632 | st = substr(st,p+length(mp)); 633 | HasMathjax = 1; 634 | } else if(mp == ">") { 635 | r = r ">"; 636 | } else if(mp == "<") { 637 | 638 | p = index(st, ">"); 639 | if(!p) { 640 | r = r "<"; 641 | continue; 642 | } 643 | tg = substr(st, 1, p - 1); 644 | if(match(tg,/^[[:alpha:]]+[[:space:]]/)) { 645 | a = trim(substr(tg,RSTART+RLENGTH-1)); 646 | tg = substr(tg,1,RLENGTH-1); 647 | } else 648 | a = ""; 649 | 650 | if(match(tolower(tg), "^/?(a|abbr|div|span|blockquote|pre|img|code|p|em|strong|sup|sub|del|ins|s|u|b|i|br|hr|ul|ol|li|table|thead|tfoot|tbody|tr|th|td|caption|column|col|colgroup|figure|figcaption|dl|dd|dt|mark|cite|q|var|samp|small|details|summary)$")) { 651 | if(!match(tg, /\//)) { 652 | if(match(a, /class="/)) { 653 | sub(/class="/, "class=\"dawk-ex ", a); 654 | } else { 655 | if(a) 656 | a = a " class=\"dawk-ex\"" 657 | else 658 | a = "class=\"dawk-ex\"" 659 | } 660 | r = r "<" tg " " a ">"; 661 | } else 662 | r = r "<" tg ">"; 663 | } else if(match(tg, "^[[:alpha:]]+://[[:graph:]]+$")) { 664 | if(!a) a = tg; 665 | r = r "" a ""; 666 | } else if(match(tg, "^[[:graph:]]+@[[:graph:]]+$")) { 667 | if(!a) a = tg; 668 | r = r "" obfuscate(a) ""; 669 | } else { 670 | r = r "<"; 671 | continue; 672 | } 673 | 674 | st = substr(st, p + 1); 675 | } else if(mp == "&") { 676 | if(match(st, /^[#[:alnum:]]+;/)) { 677 | r = r "&" substr(st, 1, RLENGTH); 678 | st = substr(st, RLENGTH+1); 679 | } else { 680 | r = r "&"; 681 | } 682 | } 683 | } 684 | return r st; 685 | } 686 | 687 | function push(newmode) {Stack[StackTop++] = Mode; Mode = newmode;} 688 | function pop() {Mode = Stack[--StackTop];Buf = ""; return Mode;} 689 | function heading(level, st, res, href, u, text,svg) { 690 | if(level > 6) level = 6; 691 | st = trim(st); 692 | href = tolower(st); 693 | href = strip_tags(href); 694 | gsub(/[^-_ [:alnum:]]+/, "", href); 695 | gsub(/[[:space:]]/, "-", href); 696 | if(TitleUrls[href]) { 697 | for(u = 1; TitleUrls[href "-" u]; u++); 698 | href = href "-" u; 699 | } 700 | TitleUrls[href] = "#" href; 701 | 702 | svg = ""; 703 | text = "" st " " svg "" (TopLinks?"  ↑ Top":""); 704 | 705 | res = tag("h" level, text, "id=\"" href "\""); 706 | for(;ToCLevel < level; ToCLevel++) { 707 | ToC_ID++; 708 | if(ToCLevel < HideToCLevel) { 709 | ToC = ToC ""; 710 | ToC = ToC "
      "; 711 | } else { 712 | ToC = ToC ""; 713 | ToC = ToC "
        "; 714 | } 715 | } 716 | for(;ToCLevel > level; ToCLevel--) 717 | ToC = ToC "
      "; 718 | ToC = ToC "
    • " st "\n"; 719 | ToCLevel = level; 720 | return res; 721 | } 722 | function process_table_row(st ,cols, i) { 723 | if(match(st, /^[[:space:]]*\|/)) 724 | st = substr(st, RSTART+RLENGTH); 725 | if(match(st, /\|[[:space:]]*$/)) 726 | st = substr(st, 1, RSTART - 1); 727 | st = trim(st); 728 | 729 | if(match(st, /^([[:space:]:|]|---+)*$/)) { 730 | IsHeaders[Row-1] = 1; 731 | cols = split(st, A, /[[:space:]]*\|[[:space:]]*/) 732 | for(i = 1; i <= cols; i++) { 733 | if(match(A[i], /^:-*:$/)) 734 | Align[i] = "center"; 735 | else if(match(A[i], /^-*:$/)) 736 | Align[i] = "right"; 737 | else if(match(A[i], /^:-*$/)) 738 | Align[i] = "left"; 739 | } 740 | return; 741 | } 742 | 743 | cols = split(st, A, /[[:space:]]*\|[[:space:]]*/); 744 | for(i = 1; i <= cols; i++) { 745 | Table[Row, i] = A[i]; 746 | } 747 | NCols[Row] = cols; 748 | if(cols > MaxCols) 749 | MaxCols = cols; 750 | IsHeaders[Row] = 0; 751 | Row++; 752 | } 753 | function end_table( r,c,t,a,s) { 754 | for(r = 1; r < Row; r++) { 755 | t = IsHeaders[r] ? "th" : "td" 756 | s = s "" 757 | for(c = 1; c <= NCols[r]; c++) { 758 | a = Align[c]; 759 | if(a) 760 | s = s "<" t " align=\"" a "\">" scrub(Table[r,c]) "" 761 | else 762 | s = s "<" t ">" scrub(Table[r,c]) "" 763 | } 764 | s = s "\n" 765 | } 766 | return tag("table", s, "class=\"da\""); 767 | } 768 | function make_toc(st, r,p,dis,t,n,tocBody) { 769 | if(!ToC) return st; 770 | for(;ToCLevel > 1;ToCLevel--) 771 | ToC = ToC "
    "; 772 | 773 | tocBody = "
      " ToC "
    \n"; 774 | 775 | p = match(st, /!\[toc[-+]?\]/); 776 | while(p) { 777 | if(substr(st,RSTART-1,1) == "\\") { 778 | r = r substr(st,1,RSTART-2) substr(st,RSTART,RLENGTH); 779 | st = substr(st,RSTART+RLENGTH); 780 | p = match(st, /!\[toc[-+]?\]/); 781 | continue; 782 | } 783 | 784 | ++n; 785 | dis = index(substr(st,RSTART,RLENGTH),"+"); 786 | t = "
    \nContents\n" \ 787 | tocBody "
    "; 788 | t = t "\n
    " tocBody "
    " 789 | r = r substr(st,1,RSTART-1); 790 | r = r t; 791 | st = substr(st,RSTART+RLENGTH); 792 | p = match(st, /!\[toc[-+]?\]/); 793 | } 794 | return r st; 795 | } 796 | function fix_links(st, lt,ld,lr,url,img,res,rx,pos,pre) { 797 | do { 798 | pre = match(st, /<(pre|code)>/); # Don't substitute in
     or  blocks
     799 |         pos = match(st, /\[[^\]]+\]/);
     800 |         if(!pos)break;
     801 |         if(pre && pre < pos) {
     802 |             match(st, /<\/(pre|code)>/);
     803 |             res = res substr(st,1,RSTART+RLENGTH);
     804 |             st = substr(st, RSTART+RLENGTH+1);
     805 |             continue;
     806 |         }
     807 |         img=substr(st,RSTART-1,1)=="!";
     808 |         if(substr(st, RSTART-(img?2:1),1)=="\\") {
     809 |             res = res substr(st,1,RSTART-(img?3:2));
     810 |             if(img && substr(st,RSTART,RLENGTH)=="[toc]")res=res "\\";
     811 |             res = res substr(st,RSTART-(img?1:0),RLENGTH+(img?1:0));
     812 |             st = substr(st, RSTART + RLENGTH);
     813 |             continue;
     814 |         }
     815 |         res = res substr(st, 1, RSTART-(img?2:1));
     816 |         rx = substr(st, RSTART, RLENGTH);
     817 |         st = substr(st, RSTART+RLENGTH);
     818 |         if(match(st, /^[[:space:]]*\([^)]+\)/)) {
     819 |             lt = substr(rx, 2, length(rx) - 2);
     820 |             match(st, /\([^)]+\)/);
     821 |             url = substr(st, RSTART+1, RLENGTH-2);
     822 |             st = substr(st, RSTART+RLENGTH);
     823 |             ld = "";
     824 |             if(match(url,/[[:space:]]+["']/)) {
     825 |                 ld = url;
     826 |                 url = substr(url, 1, RSTART - 1);
     827 |                 match(ld,/["']/);
     828 |                 delim = substr(ld, RSTART, 1);
     829 |                 if(match(ld,delim ".*" delim))
     830 |                     ld = substr(ld, RSTART+1, RLENGTH-2);
     831 |             }  else ld = "";
     832 |             if(img)
     833 |                 res = res "\""";
     834 |             else
     835 |                 res = res "" lt "";
     836 |         } else if(match(st, /^[[:space:]]*\[[^\]]*\]/)) {
     837 |             lt = substr(rx, 2, length(rx) - 2);
     838 |             match(st, /\[[^\]]*\]/);
     839 |             lr = trim(tolower(substr(st, RSTART+1, RLENGTH-2)));
     840 |             if(!lr) {
     841 |                 lr = tolower(trim(lt));
     842 |                 if(LinkDescs[lr]) lt = LinkDescs[lr];
     843 |             }
     844 |             st = substr(st, RSTART+RLENGTH);
     845 |             url = LinkUrls[lr];
     846 |             ld = LinkDescs[lr];
     847 |             if(img)
     848 |                 res = res "\""";
     849 |             else if(url)
     850 |                 res = res "" lt "";
     851 |             else
     852 |                 res = res "[" lt "][" lr "]";
     853 |         } else
     854 |             res = res (img?"!":"") rx;
     855 |     } while(pos > 0);
     856 |     return res st;
     857 | }
     858 | function fix_footnotes(st,         r,p,n,i,d,fn,fc) {
     859 |     p = match(st, /\[\^[^\]]+\]/);
     860 |     while(p) {
     861 |         if(substr(st,RSTART-2,1) == "\\") {
     862 |             r = r substr(st,1,RSTART-3) substr(st,RSTART,RLENGTH);
     863 |             st = substr(st,RSTART+RLENGTH);
     864 |             p = match(st, /\[\^[^\]]+\]/);
     865 |             continue;
     866 |         }
     867 |         r = r substr(st,1,RSTART-1);
     868 |         d = substr(st,RSTART+2,RLENGTH-3);
     869 |         n = tolower(d);
     870 |         st = substr(st,RSTART+RLENGTH);
     871 |         if(Footnote[tolower(n)]) {
     872 |             if(!fn[n]) fn[n] = ++fc;
     873 |             d = Footnote[n];
     874 |         } else {
     875 |             Footnote[n] = scrub(d);
     876 |             if(!fn[n]) fn[n] = ++fc;
     877 |         }
     878 |         footname[fc] = n;
     879 |         d = strip_tags(d);
     880 |         if(length(d) > 20) d = substr(d,1,20) "…";
     881 |         r = r "[" fn[n] "]";
     882 |         p = match(st, /\[\^[^\]]+\]/);
     883 |     }
     884 |     for(i=1;i<=fc;i++)
     885 |         footnotes = footnotes "
  • " Footnote[footname[i]] \ 886 | "  ↶ Back
  • \n"; 888 | return r st; 889 | } 890 | function fix_abbrs(str, st,k,r,p) { 891 | for(k in Abbrs) { 892 | r = ""; 893 | st = str; 894 | t = escape(Abbrs[toupper(k)]); 895 | gsub(/&/,"\\&", t); 896 | p = match(st,"[^[:alnum:]]" k "[^[:alnum:]]"); 897 | while(p) { 898 | r = r substr(st, 1, RSTART); 899 | r = r "" k ""; 900 | st = substr(st, RSTART+RLENGTH-1); 901 | p = match(st,"[^[:alnum:]]" k "[^[:alnum:]]"); 902 | } 903 | str = r st; 904 | } 905 | return str; 906 | } 907 | function tag(t, body, attr) { 908 | if(attr) 909 | attr = " " trim(attr); 910 | # https://www.w3.org/TR/html5/grouping-content.html#the-p-element 911 | if(t == "p" && (match(body, /<\/?(div|table|blockquote|dl|ol|ul|h[[:digit:]]|hr|pre)[>[:space:]]/))|| (match(body,/!\[toc\]/) && substr(body, RSTART-1,1) != "\\")) 912 | return "<" t attr ">" body "\n"; 913 | else 914 | return "<" t attr ">" body "\n"; 915 | } 916 | function itag(t, body) { 917 | return "<" t ">" body ""; 918 | } 919 | function obfuscate(e, r,i,t,o) { 920 | for(i = 1; i <= length(e); i++) { 921 | t = substr(e,i,1); 922 | r = int(rand() * 100); 923 | if(r > 50) 924 | o = o sprintf("&#x%02X;", _ord[t]); 925 | else if(r > 10) 926 | o = o sprintf("&#%d;", _ord[t]); 927 | else 928 | o = o t; 929 | } 930 | return o; 931 | } 932 | function init_css(Css, css,ss,hr,bg1,bg2,bg3,bg4,ff,fs,i,lt,dt) { 933 | if(Css == "0") return ""; 934 | 935 | css["body"] = "color:var(--color);background:var(--background);font-family:%font-family%;font-size:%font-size%;line-height:1.5em;" \ 936 | "padding:1em 2em;width:80%;max-width:%maxwidth%;margin:0 auto;min-height:100%;float:none;"; 937 | css["h1"] = "border-bottom:1px solid var(--heading);padding:0.3em 0.1em;"; 938 | css["h1 a"] = "color:var(--heading);"; 939 | css["h2"] = "color:var(--heading);border-bottom:1px solid var(--heading);padding:0.2em 0.1em;"; 940 | css["h2 a"] = "color:var(--heading);"; 941 | css["h3"] = "color:var(--heading);border-bottom:1px solid var(--heading);padding:0.1em 0.1em;"; 942 | css["h3 a"] = "color:var(--heading);"; 943 | css["h4,h5,h6"] = "padding:0.1em 0.1em;"; 944 | css["h4 a,h5 a,h6 a"] = "color:var(--heading);"; 945 | css["h1,h2,h3,h4,h5,h6"] = "font-weight:bolder;line-height:1.2em;"; 946 | css["h4"] = "border-bottom:1px solid var(--heading)"; 947 | css["p"] = "margin:0.5em 0.1em;" 948 | css["hr"] = "background:var(--color);height:1px;border:0;" 949 | css["a.normal, a.toc"] = "color:var(--alt-color);"; 950 | #css["a.normal:visited"] = "color:var(--heading);"; 951 | #css["a.normal:active"] = "color:var(--heading);"; 952 | css["a.normal:hover, a.toc:hover"] = "color:var(--alt-color);"; 953 | css["a.top"] = "font-size:x-small;text-decoration:initial;float:right;"; 954 | css["a.header svg"] = "opacity:0;"; 955 | css["a.header:hover svg"] = "opacity:1;"; 956 | css["a.header"] = "text-decoration: none;"; 957 | css["a.dark-toggle"] = "float:right; cursor: pointer; font-size: small; padding: 0.3em 0.5em 0.5em 0.5em; font-family: monospace; border-radius: 3px;"; 958 | css["a.dark-toggle:hover"] = "background:var(--alt-background);"; 959 | css[".toc-button"] = "color:var(--alt-color);cursor:pointer;font-size:small;padding: 0.3em 0.5em 0.5em 0.5em;font-family:monospace;border-radius:3px;"; 960 | css["a.toc-button:hover"] = "background:var(--alt-background);"; 961 | css["a.footnote"] = "font-size:smaller;text-decoration:initial;"; 962 | css["a.footnote-back"] = "text-decoration:initial;font-size:x-small;"; 963 | css["strong,b"] = "color:var(--color)"; 964 | css["code"] = "color:var(--alt-color);font-weight:bold;"; 965 | css["blockquote"] = "margin-left:1em;color:var(--alt-color);border-left:0.2em solid var(--alt-color);padding:0.25em 0.5em;overflow-x:auto;"; 966 | css["pre"] = "color:var(--alt-color);background:var(--alt-background);border:1px solid;border-radius:2px;line-height:1.25em;margin:0.25em 0.5em;padding:0.75em;overflow-x:auto;"; 967 | css["table.dawk-ex"] = "border-collapse:collapse;margin:0.5em;"; 968 | css["th.dawk-ex,td.dawk-ex"] = "padding:0.5em 0.75em;border:1px solid var(--heading);"; 969 | css["th.dawk-ex"] = "color:var(--heading);border:1px solid var(--heading);border-bottom:2px solid var(--heading);"; 970 | css["tr.dawk-ex:nth-child(odd)"] = "background-color:var(--alt-background);"; 971 | css["table.da"] = "border-collapse:collapse;margin:0.5em;"; 972 | css["table.da th,td"] = "padding:0.5em 0.75em;border:1px solid var(--heading);"; 973 | css["table.da th"] = "color:var(--heading);border:1px solid var(--heading);border-bottom:2px solid var(--heading);"; 974 | css["table.da tr:nth-child(odd)"] = "background-color:var(--alt-background);"; 975 | css["div.dawk-ex"] = "padding:0.5em;"; 976 | css["caption.dawk-ex"] = "padding:0.5em;font-style:italic;"; 977 | css["dl.dawk-ex"] = "margin:0.5em;"; 978 | css["dt.dawk-ex"] = "font-weight:bold;"; 979 | css["dd.dawk-ex"] = "padding:0.3em;"; 980 | css["mark.dawk-ex"] = "color:var(--alt-background);background-color:var(--heading);"; 981 | css["del.dawk-ex,s.dawk-ex"] = "color:var(--heading);"; 982 | css["div#table-of-contents"] = "padding:0;font-size:smaller;"; 983 | css["abbr"] = "cursor:help;"; 984 | css["ol.footnotes"] = "font-size:small;color:var(--alt-color)"; 985 | css[".fade"] = "color:var(--alt-background);"; 986 | css[".highlight"] = "color:var(--alt-color);background-color:var(--alt-background);"; 987 | css["summary"] = "cursor:pointer;"; 988 | css["ul.toc"] = "list-style-type:none;"; 989 | 990 | # This is a trick to prevent page-breaks immediately after headers 991 | # https://stackoverflow.com/a/53742871/115589 992 | css["blockquote,code,pre,table"] = "break-inside: avoid;break-before: auto;" 993 | css["section"] = "break-inside: avoid;break-before: auto;" 994 | css["h1,h2,h3,h4"] = "break-inside: avoid;"; 995 | css["h1::after,h2::after,h3::after,h4::after"] = "content: \"\";display: block;height: 200px;margin-bottom: -200px;"; 996 | 997 | if(NumberHeadings) { 998 | if(NumberH1s) { 999 | css["body"] = css["body"] "counter-reset: h1 toc1;"; 1000 | css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"; 1001 | css["h2"] = css["h2"] "counter-reset: h3 h4;"; 1002 | css["h3"] = css["h3"] "counter-reset: h4;"; 1003 | css["h1::before"] = "content: counter(h1) \" \"; counter-increment: h1; margin-right: 10px;"; 1004 | css["h2::before"] = "content: counter(h1) \".\"counter(h2) \" \";counter-increment: h2; margin-right: 10px;"; 1005 | css["h3::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"; 1006 | css["h4::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"; 1007 | 1008 | css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"; 1009 | css["li.toc-2"] = "counter-reset: toc3 toc4;"; 1010 | css["li.toc-3"] = "counter-reset: toc4;"; 1011 | css["a.toc-1::before"] = "content: counter(h1) \" \";counter-increment: toc1;"; 1012 | css["a.toc-2::before"] = "content: counter(h1) \".\" counter(toc2) \" \";counter-increment: toc2;"; 1013 | css["a.toc-3::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"; 1014 | css["a.toc-4::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"; 1015 | 1016 | } else { 1017 | css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"; 1018 | css["h2"] = css["h2"] "counter-reset: h3 h4;"; 1019 | css["h3"] = css["h3"] "counter-reset: h4;"; 1020 | css["h2::before"] = "content: counter(h2) \" \";counter-increment: h2; margin-right: 10px;"; 1021 | css["h3::before"] = "content: counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"; 1022 | css["h4::before"] = "content: counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"; 1023 | 1024 | css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"; 1025 | css["li.toc-2"] = "counter-reset: toc3 toc4;"; 1026 | css["li.toc-3"] = "counter-reset: toc4;"; 1027 | css["a.toc-2::before"] = "content: counter(toc2) \" \";counter-increment: toc2;"; 1028 | css["a.toc-3::before"] = "content: counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"; 1029 | css["a.toc-4::before"] = "content: counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"; 1030 | } 1031 | } 1032 | 1033 | # Font Family: 1034 | ff = "sans-serif"; 1035 | fs = "11pt"; 1036 | 1037 | for(i = 0; i<=255; i++)_hex[sprintf("%02X",i)]=i; 1038 | 1039 | # Light theme colors: 1040 | lt = "{--color: #263053; --alt-color: #16174c; --heading: #2A437E; --background: #FDFDFD; --alt-background: #F9FAFF;}"; 1041 | # Dark theme colors: 1042 | dt = "{--color: #E9ECFF; --alt-color: #9DAFE6; --heading: #6C89E8; --background: #13192B; --alt-background: #232A42;}"; 1043 | 1044 | ss = ss "\nbody " lt; 1045 | ss = ss "\nbody.dark-theme " dt; 1046 | ss = ss "\n@media (prefers-color-scheme: dark) {" 1047 | ss = ss "\n body " dt; 1048 | ss = ss "\n body.light-theme " lt; 1049 | ss = ss "\n}" 1050 | for(k in css) 1051 | ss = ss "\n" k "{" css[k] "}"; 1052 | gsub(/%maxwidth%/,MaxWidth,ss); 1053 | gsub(/%font-family%/,ff,ss); 1054 | gsub(/%font-size%/,fs,ss); 1055 | gsub(/%hr%/,hr,ss); 1056 | 1057 | return ss; 1058 | } 1059 | --------------------------------------------------------------------------------