]> git.sur5r.net Git - c128-kasse/commitdiff
patch charset to include umlauts
authorMaik Fischer <maikf@qu.cx>
Thu, 5 Oct 2017 22:50:00 +0000 (00:50 +0200)
committerMaik Fischer <maikf@qu.cx>
Sat, 14 Oct 2017 08:32:29 +0000 (10:32 +0200)
12 files changed:
.gitignore
Makefile
assets/umlauts.pbm [new file with mode: 0644]
include/vdc_patch_charset.h [new file with mode: 0644]
include/vdc_util.h [new file with mode: 0644]
src/general.c
src/kasse.c
src/print_ascii.c [new file with mode: 0644]
src/print_charmap.c [new file with mode: 0644]
src/vdc_patch_charset.c [new file with mode: 0644]
src/vdc_util.s [new file with mode: 0644]
util/mkfont [new file with mode: 0755]

index 7ffcf3d3d59e68b5dc69dec3ceb66cce9fdbbf77..a45be6e9ffd1798a931462347051ebaf16ec0a89 100644 (file)
@@ -1,13 +1,16 @@
 kasse
 .cproject
 .project
-*.s
-*.o
+build/
 *.P00
 itemz
 *.d64
 include/version.h
+include/charset_umlauts.h
 tags
 items
+credits
 cat
+charmap
+ascii
 *.lbl
index f3d0577ea72e2c2d4d7ab43995f947b4468c5347..1df105e47f6445f124df0ea960ae096f60f564eb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,11 @@
 CC=cc65
 AS=ca65
 LD=cl65
-INCLUDES:=$(wildcard include/*.h) include/version.h
+INCLUDES:=$(wildcard include/*.h) include/version.h include/charset_umlauts.h
 GV:=$(shell git describe --tags --always)
 CFLAGS= -I include -t c128 -g
 
-.PHONY: include/version.h clean dist-clean format
+.PHONY: include/version.h include/charset_umlauts.h clean dist-clean format
 
 all: kasse itemz cat
 
@@ -13,11 +13,17 @@ build/%.o: src/%.c ${INCLUDES}
        ${CC} ${CFLAGS} -O $< -o build/$(addsuffix .s,$(shell basename $< .c))
        ${AS} ${CFLAGS} build/$(addsuffix .s,$(shell basename $< .c)) -o $@
 
+build/%.o: src/%.s
+       ${AS} ${CFLAGS} $< -o $@
+
 include/version.h:
        mkdir -p build
        echo "#define GV \"${GV}\"" > $@
 
-kasse: build/config.o build/kasse.o build/general.o build/credit_manager.o build/c128time.o build/print.o
+include/charset_umlauts.h:
+       ./util/mkfont assets/umlauts.pbm chars_umlauts > $@
+
+kasse: build/config.o build/kasse.o build/general.o build/credit_manager.o build/c128time.o build/print.o build/vdc_patch_charset.o build/vdc_util.o
        ${LD} -Ln $@.lbl -t c128 $^ -o $@
 
 itemz: build/config.o build/itemz.o build/general.o build/credit_manager.o build/c128time.o build/print.o
@@ -26,6 +32,12 @@ itemz: build/config.o build/itemz.o build/general.o build/credit_manager.o build
 cat: build/general.o build/cat.o
        ${LD} -Ln $@.lbl -t c128 $^ -o $@
 
+charmap: build/print_charmap.o build/vdc_util.o build/vdc_patch_charset.o
+       ${LD} -Ln $@.lbl -t c128 $^ -o $@
+
+ascii: build/print_ascii.o
+       ${LD} -Ln $@.lbl -t c128 $^ -o $@
+
 package: all
        c1541 -format "${GV}",KA d64 kasse.d64
        c1541 -attach kasse.d64 -write kasse
diff --git a/assets/umlauts.pbm b/assets/umlauts.pbm
new file mode 100644 (file)
index 0000000..c582deb
Binary files /dev/null and b/assets/umlauts.pbm differ
diff --git a/include/vdc_patch_charset.h b/include/vdc_patch_charset.h
new file mode 100644 (file)
index 0000000..ff80c82
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __VDC_PATCH_CHARSET_H_
+#define __VDC_PATCH_CHARSET_H_
+
+void vdc_patch_charset(void);
+
+#define EURSYM "\xA2"
+#define AUML "\x9C"
+#define OUML "\x9E"
+#define UUML "\x9F"
+#define aUML "\xA6"
+#define oUML "\xA8"
+#define uUML "\xA9"
+#define szLIG "\xBF"
+
+#endif // __VDC_PATCH_CHARSET_H_
diff --git a/include/vdc_util.h b/include/vdc_util.h
new file mode 100644 (file)
index 0000000..2c10ccd
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __VDC_UTIL_H_
+#define __VDC_UTIL_H_
+
+extern void __fastcall__ vdc_load_thinfont(void);
+extern unsigned char __fastcall__ vdc_read_reg(unsigned char reg);
+extern unsigned __fastcall__ vdc_read_addr(unsigned char reg);
+extern void __fastcall__ vdc_write_reg(unsigned char reg, unsigned char data);
+extern void __fastcall__ vdc_write_addr(unsigned char reg, unsigned addr);
+extern void __fastcall__ vdc_read_mem(unsigned dest, unsigned src, unsigned n);
+extern void __fastcall__ vdc_write_mem(unsigned dest, const void *src,
+                                       unsigned n);
+extern void __fastcall__ vdc_load_thinfont(void);
+
+#endif // __VDC_UTIL_H_
index 3a3750d5b8aff6450f1b918880a02183116eb7df..ec06ef4271349565a27ad29c03899d77e6e01783 100644 (file)
@@ -10,6 +10,7 @@
 #include <conio.h>
 
 #include "general.h"
+#include "vdc_patch_charset.h"
 
 /*
  * get_input_terminated_by() reads input (handling backspace correctly) until
@@ -75,7 +76,7 @@ char retry_or_quit(void) {
 }
 
 char *format_euro(char *s, int maxlen, int cent) {
-  if (snprintf(s, maxlen, "%3d,%02dEUR", cent / 100, cent % 100) > maxlen)
+  if (snprintf(s, maxlen, "%3d,%02d" EURSYM, cent / 100, cent % 100) > maxlen)
     return NULL;
   return s;
 }
index 203d70f683ae5dfe198149227edb5911d8f1ddc7..4bed5cf5f8380ec00aaf5348b3143250e5f58a6b 100644 (file)
@@ -11,6 +11,7 @@
 #include <string.h>
 #include <cbm.h>
 #include <c128.h>
+#include <6502.h>
 
 #include "general.h"
 #include "config.h"
@@ -19,6 +20,7 @@
 #include "c128time.h"
 #include "print.h"
 #include "version.h"
+#include "vdc_patch_charset.h"
 // drucker 4 oder 5
 // graphic 4,0,10
 
@@ -32,7 +34,7 @@ void print_item(BYTE i) {
   textcolor(TC_YELLOW);
   cprintf("%2d", i);
   textcolor(TC_LIGHT_GRAY);
-  cprintf(": %-" xstr(MAX_ITEM_NAME_LENGTH) "s \xDD%s, %3dx ",
+  cprintf(": %-" xstr(MAX_ITEM_NAME_LENGTH) "s \xDD%s,   %3dx ",
           status.status[i].item_name, profit, status.status[i].times_sold);
 }
 
@@ -47,7 +49,7 @@ static void print_screen(void) {
     exit(1);
   }
   textcolor(TC_CYAN);
-  cprintf("C128-Kassenprogramm (phil_fry, sECuRE, sur5r) " GV "\r\n");
+  cprintf("C128-Kassenprogramm (phil_fry, sECuRE, sur5r, mxf) " GV "\r\n");
   textcolor(TC_LIGHT_GRAY);
   cprintf("\r\nUhrzeit:     %s (wird nicht aktualisiert)\r\n"
           "Eingenommen: %s, Verkauft: %ld Dinge, Drucken: %s\r\n",
@@ -175,7 +177,7 @@ static signed int buy(char *name, unsigned int price) {
       continue;
     }
     if (c == 27) {
-      cprintf("Kauf abgebrochen, druecke RETURN...\r\n");
+      cprintf("Kauf abgebrochen, dr" uUML "cke RETURN...\r\n");
       get_input();
       return 1;
     }
@@ -192,7 +194,7 @@ static signed int buy(char *name, unsigned int price) {
   einheiten = atoi(entered) * negative;
 
   if (einheiten > 100 || einheiten < -100 || einheiten == 0) {
-    cprintf("\r\nEinheit nicht in [-100, 100] oder 0, Abbruch, druecke "
+    cprintf("\r\nEinheit nicht in [-100, 100] oder 0, Abbruch, dr" uUML "cke "
             "RETURN...\r\n");
     cgetc();
     return 1;
@@ -304,7 +306,8 @@ static signed int buy(char *name, unsigned int price) {
       }
 
       textcolor(TC_LIGHT_GREEN);
-      cprintf("\r\nVerbleibendes Guthaben fuer %s: %s. Druecke RETURN...\r\n",
+      cprintf("\r\nVerbleibendes Guthaben f" uUML "r %s: %s. Dr" uUML
+              "cke RETURN...\r\n",
               nickname, rest);
       textcolor(TC_LIGHT_GRAY);
       get_input();
@@ -312,7 +315,7 @@ static signed int buy(char *name, unsigned int price) {
     } else {
       textcolor(TC_LIGHT_RED);
       cprintf("\r\nNickname nicht gefunden in der Guthabenverwaltung! Abbruch, "
-              "druecke RETURN...\r\n");
+              "dr" uUML "cke RETURN...\r\n");
       textcolor(TC_LIGHT_GRAY);
       get_input();
       return 0;
@@ -364,7 +367,7 @@ void buy_custom(void) {
       break;
     cputc(c);
     if (c == 27) {
-      cprintf("Kauf abgebrochen, druecke RETURN...\r\n");
+      cprintf("Kauf abgebrochen, dr" uUML "cke RETURN...\r\n");
       get_input();
       return;
     } else if (c == '-' && i == 0)
@@ -409,6 +412,10 @@ int main(void) {
   /* clock CPU at double the speed (a whopping 2 Mhz!) */
   fast();
 
+  SEI();
+  vdc_patch_charset();
+  CLI();
+
   clrscr();
 
   /* Allocate logging buffer memory */
@@ -454,10 +461,14 @@ int main(void) {
     } else if (*c == 'f') {
       buy_custom();
     } else if (*c == 's') {
+      cprintf("\r\nsaving items.. ");
       save_items();
+      cprintf("ok\r\nsaving credits.. ");
       save_credits();
+      cprintf("ok\r\nflushing log.. ");
       log_flush();
-      cprintf("\r\nStatefile/Creditfile/Log gesichert, druecke RETURN...\r\n");
+      cprintf("ok\r\nStatefile/Creditfile/Log gesichert, dr" uUML
+              "cke RETURN...\r\n");
       get_input();
     } else if (*c == 'g') {
       credit_manager();
diff --git a/src/print_ascii.c b/src/print_ascii.c
new file mode 100644 (file)
index 0000000..286dc9d
--- /dev/null
@@ -0,0 +1,54 @@
+#include <stdlib.h>
+#include <conio.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <c128.h>
+
+//#include "vdc_patch_charset.h"
+
+int main(void) {
+  int i = 80;
+  unsigned char c[3] = {0x20, 0x20, 0};
+  unsigned char pos[5];
+
+  videomode(0x80);
+  fast();
+  clrscr();
+
+  cputsxy(4, 0, "0 1 2 3 4 5 6 7 8 9 A B C D E F");
+  cputs("\r\n");
+
+  /* unrolled, so compiler can do its magick */
+  cputs("20  \x20 \x21 \x22 \x23 \x24 \x25 \x26 \x27 \x28 \x29 \x2a \x2b \x2c "
+        "\x2d \x2e \x2f \r\n");
+  cputs("30  \x30 \x31 \x32 \x33 \x34 \x35 \x36 \x37 \x38 \x39 \x3a \x3b \x3c "
+        "\x3d \x3e \x3f \r\n");
+  cputs("40  \x40 \x41 \x42 \x43 \x44 \x45 \x46 \x47 \x48 \x49 \x4a \x4b \x4c "
+        "\x4d \x4e \x4f \r\n");
+  cputs("50  \x50 \x51 \x52 \x53 \x54 \x55 \x56 \x57 \x58 \x59 \x5a \x5b \x5c "
+        "\x5d \x5e \x5f \r\n");
+  cputs("60  \x60 \x61 \x62 \x63 \x64 \x65 \x66 \x67 \x68 \x69 \x6a \x6b \x6c "
+        "\x6d \x6e \x6f \r\n");
+  cputs("70  \x70 \x71 \x72 \x73 \x74 \x75 \x76 \x77 \x78 \x79 \x7a \x7b \x7c "
+        "\x7d \x7e \x7f \r\n");
+  cputs("80  \x80 \x81 \x82 \x83 \x84 \x85 \x86 \x87 \x88 \x89 \x8a \x8b \x8c "
+        "\x8d \x8e \x8f \r\n");
+  cputs("90  \x90 \x91 \x92 \x93 \x94 \x95 \x96 \x97 \x98 \x99 \x9a \x9b \x9c "
+        "\x9d \x9e \x9f \r\n");
+  cputs("a0  \xa0 \xa1 \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 \xa8 \xa9 \xaa \xab \xac "
+        "\xad \xae \xaf \r\n");
+  cputs("b0  \xb0 \xb1 \xb2 \xb3 \xb4 \xb5 \xb6 \xb7 \xb8 \xb9 \xba \xbb \xbc "
+        "\xbd \xbe \xbf \r\n");
+  cputs("c0  \xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc "
+        "\xcd \xce \xcf \r\n");
+  cputs("d0  \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc "
+        "\xdd \xde \xdf \r\n");
+  cputs("e0  \xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec "
+        "\xed \xee \xef \r\n");
+  cputs("f0  \xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 \xf8 \xf9 \xfa \xfb \xfc "
+        "\xfd \xfe \xff \r\n");
+
+  // cputs(EURSYM aUML oUML uUML AUML OUML UUML szLIG "\r\n");
+  return 0;
+}
diff --git a/src/print_charmap.c b/src/print_charmap.c
new file mode 100644 (file)
index 0000000..be8ca18
--- /dev/null
@@ -0,0 +1,66 @@
+#include <stdlib.h>
+#include <conio.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <c128.h>
+#include <6502.h>
+
+#include "vdc_util.h"
+#include "vdc_patch_charset.h"
+
+int main(void) {
+  int i = 80;
+  unsigned char c[2] = {0x00, 0x20};
+  unsigned char pos[5];
+
+  /* char attribute, alternate char set, white, full intensity.
+   * set to 0x0f for normal char set
+   */
+  unsigned char blank = 0x8f;
+
+  videomode(0x80);
+  fast();
+  clrscr();
+
+  cputsxy(4, 0, "0 1 2 3 4 5 6 7 8 9 A B C D E F");
+
+  /* Manipulate the VDC with IRQs turned off.
+   * KERNALs default IRQ handler will also try to read the VDC status
+   * register, which could interfere with our code trying to read it.
+   */
+  SEI();
+
+  // vdc_load_thinfont();
+  vdc_patch_charset();
+
+  /* write 16 chars per line */
+  do {
+    if ((*c % 16) == 0) {
+      sprintf(pos, "%02x  ", *c);
+      vdc_write_mem(i, pos, 4);
+      i = i + 4;
+    }
+
+    vdc_write_mem(i, c, 2);
+    ++(*c);
+    i = i + 2;
+
+    if ((*c % 16) == 0) {
+      i = i + 44;
+    }
+  } while (*c);
+
+  /* clear attribute mem */
+  i = 0;
+  while (++i <= 2000)
+    vdc_write_mem(i + 0x800, &blank, 1);
+
+  CLI();
+
+  /* set cursor, so basic's prompt won't overwrite our output */
+  gotoxy(0, 18);
+  cputs(EURSYM aUML oUML uUML AUML OUML UUML szLIG);
+  return 0;
+}
diff --git a/src/vdc_patch_charset.c b/src/vdc_patch_charset.c
new file mode 100644 (file)
index 0000000..c985aa3
--- /dev/null
@@ -0,0 +1,18 @@
+#include <string.h>
+
+#include "charset_umlauts.h"
+#include "vdc_util.h"
+
+void vdc_patch_charset(void) {
+  int i = 0;
+  /* start of the shifted charset */
+  const unsigned int base_addr = 0x3000;
+  /*                      EUR   Ä     Ö     Ü     ä     ö     ü     ß */
+  unsigned char map[8] = {0x62, 0x5C, 0x5E, 0x5F, 0x66, 0x68, 0x69, 0x7F};
+  unsigned char *p = map;
+
+  for (; i < sizeof(chars_umlauts); i += 8) {
+    vdc_write_mem(base_addr + *p * 16, chars_umlauts + i, 8);
+    ++p;
+  }
+}
diff --git a/src/vdc_util.s b/src/vdc_util.s
new file mode 100644 (file)
index 0000000..7ff3ccf
--- /dev/null
@@ -0,0 +1,191 @@
+;;; -*- tab-width: 8; -*-
+        .export         _vdc_read_reg, _vdc_read_addr, _vdc_read_mem
+        .export         _vdc_write_reg, _vdc_write_addr, _vdc_write_mem
+        .export         _vdc_load_thinfont
+        .import         popa, popax
+        .importzp       ptr1, ptr2
+        .include        "c128.inc"
+        .debuginfo      on
+
+;;; useful documentation:
+;;; - 6502 instruction/addressing mode overview
+;;;     - http://www.obelisk.me.uk/6502/index.html
+;;; - cc65 assembler interfacing with C
+;;;     - https://github.com/cc65/wiki/wiki/Parameter-passing-and-calling-conventions
+;;;     - https://github.com/cc65/wiki/wiki/Parameter-and-return-stacks
+;;;     - https://github.com/cc65/wiki/wiki/Using-runtime-zeropage-locations-in-assembly-language
+;;; - Programming the VDC
+;;;     - Chapter 10 of http: //www.pagetable.com/docs/Commodore%20128%20Programmer%27s%20Reference%20Guide.pdf
+
+VDC_ADDR_REG    := 19
+VDC_MEM_REG     := 31
+
+;;; unsigned char __fastcall__ vdc_read_reg (unsigned char reg);
+_vdc_read_reg:
+        ldx #0                  ; clear high byte
+vdc_read_reg:       
+        sta VDC_INDEX
+
+@wait:  bit VDC_INDEX           ; busy wait until vdc is ready
+        bpl @wait
+
+        lda VDC_DATA
+        rts
+
+;;; unsigned __fastcall__ vdc_read_addr (unsigned char reg);
+_vdc_read_addr:
+        tay                     ; save copy of vdc reg
+        jsr vdc_read_reg
+        tax                     ; save high byte
+        dey                     ; set low byte vdc reg
+        tya
+        jsr vdc_read_reg
+        rts
+
+;;; void __fastcall__ vdc_write_reg (unsigned char reg, unsigned char data);
+_vdc_write_reg:
+        pha
+        jsr popa
+        tay
+        pla
+vdc_write_reg:
+        sty VDC_INDEX
+
+@wait:  bit VDC_INDEX           ; busy wait until vdc is ready
+        bpl @wait
+
+        sta VDC_DATA
+        rts
+
+;;; void __fastcall__ vdc_write_addr (unsigned char reg, unsigned addr);
+_vdc_write_addr:
+        pha
+        jsr popa
+        tay
+        pla
+vdc_write_addr:
+        jsr vdc_write_reg
+        txa                     ; get high byte of addr
+        dey
+        jsr vdc_write_reg
+        rts
+
+;;; void __fastcall__ vdc_read_mem (unsigned dest, unsigned src, unsigned n);
+_vdc_read_mem:
+        sta ptr1                ; store n
+        stx ptr1+1
+
+        jsr popax
+        ldy #VDC_ADDR_REG
+        jsr vdc_write_addr
+
+        jsr popax
+        sta ptr2
+        stx ptr2+1
+
+        lda #VDC_MEM_REG
+        sta VDC_INDEX
+
+        ldy #0                  ; offset into dest
+
+        ;; first, loop over the high byte of n, 256 times
+        ldx ptr1+1              ; get high byte of n
+        beq @low                ; skip if zero
+
+@cpyhi: bit VDC_INDEX
+        bpl @cpyhi
+
+        lda VDC_DATA
+        sta (ptr2),y
+        iny
+        bne @cpyhi              ; have we copied 256 bytes yet?
+
+        inc ptr2+1              ; adjust dest pointer
+        dex
+        bne @cpyhi              ; read 256 more bytes
+        
+@low:   ldx ptr1                ; get low byte of n
+        beq @done               ; skip if zero
+        
+@cpy:   bit VDC_INDEX
+        bpl @cpy
+
+        lda VDC_DATA
+        sta (ptr2),y
+        iny
+        dex
+        bne @cpy
+
+@done:  rts
+
+;;; void __fastcall__ vdc_write_mem (unsigned dest, const void* src, unsigned n);
+_vdc_write_mem:
+        sta ptr1                ; store n
+        stx ptr1+1
+
+        jsr popax
+        sta ptr2
+        stx ptr2+1
+
+        jsr popax
+        ldy #VDC_ADDR_REG
+        jsr vdc_write_addr
+
+        lda #VDC_MEM_REG
+        sta VDC_INDEX
+
+        ldy #0                  ; offset into dest
+
+        ;; first, loop over the high byte of n
+        ldx ptr1+1              ; get high byte of n
+        beq @low                ; skip if zero
+
+@cpyhi: bit VDC_INDEX
+        bpl @cpyhi
+
+        lda (ptr2),y
+        sta VDC_DATA
+        iny
+        bne @cpyhi              ; have we copied 256 bytes yet?
+
+        inc ptr2+1              ; adjust dest pointer
+        dex
+        bne @cpyhi              ; read 256 more bytes
+        
+@low:   ldx ptr1                ; get low byte of n
+        beq @done               ; skip if zero
+        
+@cpy:   bit VDC_INDEX
+        bpl @cpy
+
+        lda (ptr2),y
+        sta VDC_DATA
+        iny
+        dex
+        bne @cpy
+
+@done:  rts
+
+;;; void __fastcall__ vdc_load_thinfont (void);
+_vdc_load_thinfont:
+        ;; save MMU register
+        lda $0
+        pha
+        lda $1
+        pha
+
+        ;; map in alternate font
+        lda #$FF
+        sta $0
+        lda #$33
+        sta $1
+
+        ;; call kernal load font routine
+        jsr $FF62
+        
+        ;; restore MMU
+        pla
+        sta $1
+        pla
+        sta $0
+        rts
diff --git a/util/mkfont b/util/mkfont
new file mode 100755 (executable)
index 0000000..5af87c4
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use v5.10;
+
+my ($fname, $array_name) = @ARGV;
+
+die "Usage: $0 <file>\n" unless $fname;
+
+my $fh;
+open $fh, '<', $fname or die "$0: $!\n";
+
+my $header = <$fh>;
+die "$0: $fname: unknown file format\n" unless $header eq "P4\n";
+
+my $len;
+{
+    my $line = <$fh>;
+    chomp $line;
+    last if ($len) = $line =~ /^8 (\d+)$/;
+    redo if $line =~ /^#/;
+    die "$0: $fname: couldn't parse header\n";
+}
+
+local $/;
+# read rest of file and return a list of the ascii value for each byte
+my @bin = map { ord($_) } split //, <$fh>;
+
+die "$0: $fname should have $len bytes image data, got: " . scalar(@bin) . "\n"
+    unless scalar(@bin) == $len;
+
+say "/* autogenerated by util/mkfont */";
+say "const unsigned char $array_name\[$len] = {";
+
+# break up in max. 8 bytes per line
+while (my @line = splice @bin, 0, 8) {
+    # seperated by commata, depending on how many bytes we have
+    my $hexify = join ', ', ('0x%02x') x scalar(@line);
+    printf "  $hexify" , @line;
+    # if there's more bytes left, seperate next block by a comma
+    print scalar(@bin) > 0 ? ",\n" : "\n";
+}
+
+say "};"