diff options
| author | user <user@clank> | 2026-09-24 13:56:42 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 13:56:42 +0200 |
| commit | 0fa1b946ec5fd3f5cba49215ea9ab62c4004aa53 (patch) | |
| tree | 2d50213ab441f2543ba129241ceda29c5d9b496d | |
| parent | ircd: enforce unique nicknames (433), announce renames, fold case (diff) | |
| download | gbos-0fa1b946ec5fd3f5cba49215ea9ab62c4004aa53.tar.gz gbos-0fa1b946ec5fd3f5cba49215ea9ab62c4004aa53.tar.xz gbos-0fa1b946ec5fd3f5cba49215ea9ab62c4004aa53.zip | |
usr: xxd - hex dump a file or stdin
Eight bytes per line rather than the usual sixteen: offset + 8 hex pairs +
the ASCII column is 38 of the terminal's 40 columns, where 16 would wrap
every line and make the dump unreadable. -c overrides it.
No -s (skip): there is no seek syscall, so it streams from the start - the
same reason tail(1) has to keep a ring.
/# xxd h
0000: 48 65 6c 6c 6f 20 67 62 Hello gb
0008: 6f 73 0d 0a os..
blk(1) could already peek at raw disk blocks; nothing could look inside a
file. Verified against known bytes, from stdin, with -c, through a pipe, and
on a missing file.
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | include/gbos.inc | 1 | ||||
| -rw-r--r-- | src/programs.asm | 7 | ||||
| -rw-r--r-- | usr/xxd.c | 82 |
5 files changed, 92 insertions, 2 deletions
@@ -29,7 +29,7 @@ PROGS := chello echo true false uname pid cat wc head args \ ls save rm sh ps kill spin blk mkdir count ptest necho \ wget chat netd ping nslookup dhcp irc uptime ansi \ grep more cp nc httpd ircd \ - df mv sleep tee tail help poweroff + df mv sleep tee tail help poweroff xxd CBLOBS := $(patsubst %,$(BUILD)/usr/%.bin,$(PROGS)) $(BUILD)/programs.o: $(CBLOBS) @@ -49,7 +49,7 @@ into the on-screen console. Regenerate it anytime with `tools/gbdemo` (below).* way to stop the machine (the battery save is written on a clean exit). - **A C toolchain**: userland is plain C compiled with SDCC (`sm83`); ~45 tools ship in `usr/` (`cat`, `wc`, `head`, `tail`, `grep`, `cp`, `mv`, - `tee`, `more`, `ls`, `df`, `sleep`, `uptime`, …) — `help` lists them all on + `tee`, `more`, `ls`, `df`, `xxd`, `sleep`, `uptime`, …) — `help` lists them all on the Game Boy itself. Adding a program is one `.c` file + a table entry. ## Quick start diff --git a/include/gbos.inc b/include/gbos.inc index 76981da..1af3fc8 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -330,5 +330,6 @@ DEF PROG_TEE EQU 42 DEF PROG_TAIL EQU 43 DEF PROG_HELP EQU 44 DEF PROG_POWEROFF EQU 45 +DEF PROG_XXD EQU 46 ENDC diff --git a/src/programs.asm b/src/programs.asm index 11da2b1..a602350 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -178,6 +178,9 @@ ProgHelp: SECTION "prog_poweroff", ROMX[$4000], BANK[47] ProgPoweroff: INCBIN "build/usr/poweroff.bin" +SECTION "prog_xxd", ROMX[$4000], BANK[48] +ProgXxd: + INCBIN "build/usr/xxd.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - the shell, now written in C (usr/sh.c): parses >/</| and @@ -312,6 +315,8 @@ ProgramTable:: dw ProgHelp db LOW(BANK(ProgPoweroff)), HIGH(BANK(ProgPoweroff)) dw ProgPoweroff + db LOW(BANK(ProgXxd)), HIGH(BANK(ProgXxd)) + dw ProgXxd ProgramTableEnd:: ; ----------------------------------------------------------------------------- @@ -411,4 +416,6 @@ NameTable:: db PROG_HELP db "poweroff", 0 db PROG_POWEROFF + db "xxd", 0 + db PROG_XXD db 0 diff --git a/usr/xxd.c b/usr/xxd.c new file mode 100644 index 0000000..8e255ae --- /dev/null +++ b/usr/xxd.c @@ -0,0 +1,82 @@ +#include "gbos.h" +/* xxd [-c N] [file]: hex dump a file, or stdin. + * + * Eight bytes per line, not the usual sixteen: offset + 8 hex pairs + the + * ASCII column is 38 of the terminal's 40 columns, where 16 would wrap every + * line and make the dump unreadable. -c overrides it anyway. + * + * There is no seek syscall, so this streams from the start - which is also + * why there is no -s (skip). */ + +#define MAXC 16 + +static char hexd(unsigned char v) { return (char)(v < 10 ? '0' + v : 'a' + (v - 10)); } +static void hex2(unsigned char v) { + putc(hexd((unsigned char)(v >> 4))); + putc(hexd((unsigned char)(v & 15))); +} +static void hex4(unsigned int v) { + hex2((unsigned char)(v >> 8)); + hex2((unsigned char)v); +} + +void main(void) { + char *argv[8]; + unsigned char buf[MAXC]; + unsigned char argc = argv_parse(argv, 8); + char *cv = optval(argv, argc, 'c'); + unsigned char cols = 8, fd = NOFD, n = 0, i; + unsigned int off = 0; + char *fname = 0; + int ch; + + if (cv) { + cols = atou(cv); + if (cols == 0 || cols > MAXC) cols = 8; + } + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-') continue; + if (cv == argv[i]) continue; + fname = argv[i]; + break; + } + if (fname) { + fd = open(fname, O_READ); + if (fd == EISDIR) { + puts("xxd: is a directory"); + nl(); + sexit(2); + } + if (fd == NOFD) { + puts("xxd: no such file"); + nl(); + sexit(2); + } + } + + for (;;) { + if (fd == NOFD) { + char c = readc(); + ch = (c == EOF) ? -1 : c; + } else ch = fgetc(fd); + if (ch >= 0) buf[n++] = (unsigned char)ch; + if (n == cols || (ch < 0 && n)) { /* full line, or the last one */ + hex4(off); + puts(": "); + for (i = 0; i < cols; i++) { + if (i < n) hex2(buf[i]); + else { + putc(' '); + putc(' '); + } /* pad so ASCII stays aligned */ + putc(' '); + } + for (i = 0; i < n; i++) putc((buf[i] >= 32 && buf[i] < 127) ? (char)buf[i] : '.'); + nl(); + off += n; + n = 0; + } + if (ch < 0) break; + } + if (fd != NOFD) close(fd); +} |
