7028 stories
·
165 followers

Turning a Toy Game Boy into a Real Game Boy

1 Comment

In the world of children’s toys there are many offerings which are meant to look like devices used by older kids or even adults, with the Fisher Price Laugh & Learn Lil’ Gamer toy bearing quite the resemblance to Nintendo’s iconic Game Boy. Although this factoid could be filed away as amusing trivia before passing said toy to a child for its requisite physical abuse by said child, a purported adult can still have a lot of fun with this toy by modding it into a real Game Boy, as [KOUZEX] recently did.

Part of the challenge here is to not just treat it as an unconventional replacement shell for a genuine Game Boy, but to retain as much of the child toy’s look and feel as possible. This includes things like buttons and even the weird sliding blocks on the side.

For the functional components a Game Boy Color with a busted screen was chosen as a donor, with the GBC mainboard fitting almost perfectly inside its new shell. Wires were then soldered to bridge Nintendo’s PCB with the toy’s PCB to make the original buttons and speaker work. After blowing a fuse on the GBC mainboard due to likely some power back feeding, the toy’s PCB had its non-essential parts stripped, but fortunately without further damage to the grafted in electronics.

Most of this mod is quite straightforward, just with some creativity required to add a Select and Start button as these were notably absent from the original. The new, rather large replacement OLED screen is a nice upgrade too and actually fits pretty well with the chunky look of the child’s toy. Even as mostly a joke mod, it seems surprisingly functional.

Read the whole story
jepler
1 hour ago
reply
I'm a real (game) boy
Earth, Sol system, Western spiral arm
Share this story
Delete

Caller-specific coverage

1 Comment

I’ve had an idea rattling around to get more detail from coverage measurement. Can we measure the coverage in a function separately for each caller of the function?

Here’s why I want it: in Acidica, my toy BASIC interpreter, I had code to implement the built-in functions that looked something like this:

match func_name:


    case "LEN":
        if len(args) != 1:
            raise TypeError(f"Wrong arguments for LEN, got {len(args)}")
        return len(args[0])

    case "LEFT$":
        if len(args) != 2:
            raise TypeError(f"Wrong arguments for LEFT$, got {len(args)}")
        return args[0][:args[1]]

    # ... 19 other built-ins ...

I didn’t like the repeated code here: each different func_name has to check that it got its expected number of arguments and perhaps raise an error. So I refactored:

def expects(nargs: int, func_name: str, args: tuple) -> None:

    if len(args) != nargs:
        raise TypeError(f"Wrong arguments for {func_name}, got {len(args)}")

match func_name:
    case "LEN":
        expects(1, func_name, args)
        return len(args[0])

    case "LEFT$":
        expects(2, func_name, args)
        return args[0][:args[1]]

Nice. The code is tighter, easier to read, and common behavior is implemented in one place.

But the old code had an advantage: because each error condition had its own raise line, coverage measurement could tell me whether I had tested every func_name for the wrong number of arguments. With the error handling happening in a helper function, that information is lost. I’ll know that some func_name had a test for the wrong number of arguments, but not that all of them did.

Here’s where the new idea comes in. What if I could indicate that for the expects function, I want separate coverage data for each distinct calling site? Then I could see that every func_name had a test for both the wrong number of arguments and the right number of arguments. The simple branch inside expects would be measured separately for each caller.

I have a quick proof-of-concept. A decorator on expects does the work. Coverage.py already has dynamic contexts which are used for things like tracking which tests called which code. The decorator starts a new context named for the calling location, then restores the context when the function returns:

def coverage_per_caller(func):

    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        cov = coverage.Coverage.current()
        name = func.__name__
        caller = inspect.currentframe().f_back
        file = caller.f_code.co_filename
        lineno = caller.f_lineno
        prev_context = cov.switch_context(f"per_caller:{name}:{file}:{lineno}")
        try:
            ret = func(*args, **kwargs)
        finally:
            cov.switch_context(prev_context)
        return ret

    return _wrapper

I had to make one tiny (unreleased) change to coverage.py for this: switch_context used to return None, but now it returns the previous context so that we can nest them properly.

To my delight, this works! I can look at the HTML coverage report and see the caller contexts for the lines in expects. I can see that 20 callers ran the if line, but only 2 ran the raise, and the context names show the file and line number of the callers for each:

HTML report showing the contexts that ran each line of expects()

This isn’t the whole solution yet. Things to improve:

  • I’d like to post-process these contexts to show which callers were missing lines inside expects. What I’m looking for is the same kind of “this line is missing” information that I got from the original inlined logic.
  • These per-caller contexts overwrite the contexts we were already collecting (the test names). Ideally we’d have some kind of sub-context so that we could track both (or many) at once.
  • It’s not great that I had to add a decorator to the source code. Driving this through the coverage configuration would keep these kinds of details out of the source.

But it’s a start, and gives me other ideas. I could use some aspect of the data passed into a function as the context name. In this example, we could have used func_name as the context instead of the caller’s location. Maybe you have ideas for other uses.

Read the whole story
jepler
20 hours ago
reply
This is an interesting situation I had not considered.

I'm most tempted to push back on the question of whether (when you've factored out "check for correct # of arguments") like this, one benefit is .. you can stop worrying about this!

The second piece of push-back I have is in two parts: First, that you have to manually check that the number of sites on line 430 and 431 are equal, not different, so it's not that great. Second, this is likely to miss the most likely mistake I can see: Failing to have a call to 'expects' at all. In this case, the line 430 and line 431 counts would be equal. so actually you have to manually count the expected number of call sites and compare to them to the number of line 430 hits...

In my own recent interpreted language implementation, I chose to use decorators *on the functions implementing each operation* to perform argument validation by function wrapping. It just so happens that the non-wrapped function would most likely fail to work at all, so I'm not too worried about missing one in testing.
Earth, Sol system, Western spiral arm
Share this story
Delete

Executable Emoji

1 Comment
Warning: This page contains hundreds of emoji. If you're using a screen reader, be sure it doesn't read them all out loud.

A whole bunch of emoji. What could they mean?

This particular post comes out of left field a bit. I was playing around with a web application I had made - an online disassembler for the x86 - when I noticed that emoji were being encoded into the url. 

I pasted a goat emoji, ๐Ÿ  and I noticed the encoding %F0%9F%90%90.

Now, if you're familiar with x86 assembly language at all, hexadecimal 90h is probably familiar to you. It's the opcode for a null operation or NOP.

I had a brief nerd chuckle over the thought that goats were the NOPs of emoji, but then I got curious. F0h on the 8088 is the LOCK prefix. This prefix is generally used to coordinate exclusive bus access with a coprocessor such as the 8087, but otherwise does nothing for most instructions on the 8088 and is ignored (this would change on later Intel CPUs). That leaves us with 9Fh

9Fh is LAHF

The entire goat emoji is valid 8088 machine code, a sequence that reads

lock lahf
nop
nop

As it turns out, the vast majority of emoji graphemes, as they are called, start with the sequence F09F. A dim little light bulb started to flicker above my head.  Could you actually write an 8088 program using nothing but displayable emoji?  

The idea is not without precedent. It has been well-established that executables can be generated with only printable ASCII characters - the most famous example probably being the EICAR test file, an ASCII string that is also a valid DOS executable that prints "EICAR-STANDARD-ANTIVIRUS-TEST-FILE!" and exits.

Other small ASCII programs were printed in magazines or distributed in other ways, such as the tiny terminal utility TCOM, the entire source of which is reproduced below:

XPHPD[0GG0G,0G51G31GB'(G+(G:u'0g?(G>(GE1G@arwIV_F*=US@>1|_,5wXNg-7muTu(4
1m0ss1k260s@3G1g360@3G0i7t2g3A1g350@3G2E1=0C1g350@3T2M0^\1g3>0@3T=1s2g0T
1g3;0@3ToN2g391g0t@3G0^F1k0s2?0@3T4

This is an interesting "emergency terminal" solution: if someone had no other means of loading an executable onto a computer system, it could simply be entered in via the keyboard. 

It surprises me that the idea of directly executing emoji has apparently never been explored.

Hello (World)!

Of course, the first thing to do is attempt Hello World! in emoji. For space reasons and partly due to the pain of doing any sort of arithmetic in emoji, we will only print the string HELLO.

Here is the full program:

๐Ÿธโ˜บ๏ธ๐Ÿฐ๐ŸŽโ™๐Ÿ—ƒ๏ธ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿฎ๐Ÿ’—๐Ÿฆฎ๐Ÿชโ™๐Ÿฐ๐Ÿน๐Ÿ—ƒ๏ธ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿ’—๐Ÿช—๐Ÿงฏ๐Ÿ˜—๐Ÿงฎ๐Ÿซช๐Ÿ˜—๐Ÿงฎ๐Ÿ˜—๐Ÿฎ๐Ÿ˜ช๐Ÿ˜”โญ

Pasted into a text editor and saved as UTF-8, no BOM, with a .COM file extension, the result should be 141 bytes with an MD5 sum of 0a5c91475ca2de33e36aacc2f0b7b840.

The disassembly of the entire program can be viewed here.

Several emoji here may display as tofu, depending on your browser and what year you are reading this article.

๐Ÿช is the shovel emoji, introduced in Unicode 16.0 in 2024.  These glyphs still take time to trickle down into font updates.

๐Ÿช— is the accordion emoji, added to Unicode 13.0 in 2020, but somehow still not visible in Chrome on Windows 10. Go figure.

๐Ÿซช is the "Distorted Face" emoji and is brand new in Unicode 17.0, approved in 2025. 

In theory, it should be possible to copy the relevant tofu character and preserve the representational bytes, but some operating systems and programs seem to struggle with the byte-preserving concept.

This program relies on a few undocumented 8088 aliases, and so requires a fairly accurate 8088 core to execute successfully. Let's see what it does in DOSBox-X with cpu cputype=8086:

The "Hello (World!)" program executing in DosBox-X

Note the program starts with ๐Ÿธโ˜บ๏ธ. This sequence does some important setup and explains how we get a pointer to video memory. These emoji represent the byte sequence F09F90B8E298BAEFB88F

00000000  F0 9F     lahf
00000002  90        nop
00000003  B8 E2 98  mov ax,98E2h
00000006  BA EF B8  mov dx,B8EFh
00000009  8F        db 0x8F
B8EFh is still within the base B800 text mode video segment, approximately 3,824 bytes into the screen, which explains why our text appears in the bottom-right corner of the screen. Beggars can't be choosers, though, so we'll just pretend our text positioning was entirely intentional.

The ๐Ÿ’— emoji, F09F9297, moves our video segment into DI. 

0000012C  F0 9F  lahf
0000012E  92     xchg dx,ax
0000012F  97     xchg di,ax
The letters are synthesized starting with H.

๐Ÿ—ƒ๏ธ, F09F9783EFB88F, comes in clutch here:
00000146  83 EF B8  sub di,FFB8h
Subtraction by FFB8h is equivalent to addition by 48h. What's H's ASCII hex code? 48h. Neat. 

The rest of the letters are awkwardly synthesized one by one. L can, of course, be repeated. You'll note one of the Ls is green. This is caused by allowing one of the LAHF instructions to overwrite AH. It just so happens that the contents of the flag register represent a visible character attribute byte - in this case, green. The attribute could be reset at the expense of a few more bytes, but I kind of like the mismatch as a tiny hint of the cursed things going on behind the scenes.

Emojissembly Reference

If you're feeling bold enough to experiment with writing emoji code yourself, the following references may help.

Standalone Emoji

There are a few standalone emoji that represent useful instructions or instruction pairs.

Emoji UTF-8 bytes 8088 interpretation Useful effect and typical use
๐Ÿ F0 9F 90 90 LAHF; NOP; NOP Four-byte padding. Also useful inside a loop body when an exact branch displacement is needed.
๐Ÿฎ F0 9F 90 AE LAHF; NOP; SCASB DI += 1. A small, clean pointer increment.
๐Ÿ˜ฏ F0 9F 98 AF LAHF; CBW; SCASW DI += 2. Denser than two ๐Ÿฎ; AX becomes the sign extension of AL.
๐Ÿงฎ F0 9F A7 AE LAHF; CMPSW; SCASB SI += 2, DI += 3. Useful when SI may also advance. Reads DS:SI and ES:DI and changes flags.
๐Ÿงฏ F0 9F A7 AF LAHF; CMPSW; SCASW SI += 2, DI += 4. A compact four-byte advance. Also useful in the event of ๐Ÿ”ฅ.
๐Ÿค F0 9F A4 90 LAHF; MOVSB; NOP Copies one byte from DS:SI to ES:DI, then increments both pointers.
๐Ÿ˜ฌ F0 9F 98 AC LAHF; CBW; LODSB Loads one byte from DS:SI into AL, then increments SI.
๐Ÿช F0 9F 90 AA LAHF; NOP; STOSB Writes AL to ES:DI, then increments DI.
๐Ÿ˜– F0 9F 98 96 LAHF; CBW; XCHG AX,SI Moves the sign-extended AL into SI while saving the old SI in AX. Useful after obtaining a known zero.
๐Ÿ˜— F0 9F 98 97 LAHF; CBW; XCHG AX,DI Transfers a sign-extended byte between AX and DI. Useful for turning AL=F0h into DI=FFF0h.
๐Ÿ“— F0 9F 93 97 LAHF; XCHG AX,BX; XCHG AX,DI Rotates values through AX, BX, and DI. The byte writer uses it to restore a saved output pointer and recover the synthesized byte in AL.
๐Ÿ‘๏ธ F0 9F 91 81 EF B8 8F LAHF; XCHG AX,CX; SUB DI,8FB8h Adds 7048h to DI. Excellent for large modular pointer movements; clobbers AX and CX.

Open-Tail Emoji

Things get particularly interesting when we string emoji together from an emoji that leaves an "open tail" or incomplete instruction byte. The following emoji leave a dangling byte that can be very useful depending on the emoji that follows it:
 
Emoji UTF-8 bytes Open tail Common use
๐Ÿธ F0 9F 90 B8 MOV AX,imm16 needs two bytes Absorbs the beginning of โ˜บ๏ธ to create MOV AX,98E2h.
โ˜บ๏ธ E2 98 BA EF B8 8F First two bytes can be an immediate; its final 8Fh needs a ModR/M After ๐Ÿธ, supplies MOV AX,98E2h; MOV DX,B8EFh and opens a POP.
๐Ÿฐ F0 9F 90 B0 MOV AL,imm8 needs one byte Consumes a following F0h, or consumes the E2h at the start of โ˜ƒ๏ธ.
๐Ÿน F0 9F 90 B9 MOV CX,imm16 needs two bytes Consumes a following F0 9F header to load CX=9FF0h.
๐ŸŽ F0 9F 90 8E MOV Sreg,r/m16 needs a ModR/M byte Followed by the E2h from โ™, gives the 8088-only alias MOV ES,DX.
๐Ÿ˜ฟ F0 9F 98 BF MOV DI,imm16 needs two bytes Consumes the next emoji's F0 9F header and resets DI=9FF0h.
๐Ÿฝ๏ธ F0 9F 8D BD EF B8 8F POP r/m16 needs a ModR/M byte First adds B8EFh to DI; a following F0h completes undocumented POP AX, so SP += 2.
โ˜ƒ๏ธ E2 98 83 EF B8 8F Begins and ends with bytes meant for neighbors After ๐Ÿฐ consumes its E2h, the middle performs SUB DI,-72; the trailing 8Fh needs a ModR/M.
๐Ÿ—“๏ธ F0 9F 97 93 EF B8 8F Final B8 8F consumes the next F0h Saves the output pointer in BX while moving the byte accumulator into DI. It also executes OUT DX,AX.
โญ E2 AD 90 Its first two bytes are LOOP -83 A three-byte loop tail when the loop body has been laid out at exactly the right displacement.

Useful Gadgets

These are complete, useful sequences of multiple emoji.

BunnySad: ๐Ÿฐ๐Ÿ˜” - Stack repair

This eight-byte sequence deliberately replaces the current stack pointer with FFF0h:

0000: F0 9F    lahf
0002: 90       nop
0003: B0 F0    mov al,F0h
0005: 9F       lahf
0006: 98       cbw
0007: 94       xchg sp,ax

The rabbit's final B0h consumes the sad face's leading F0h, loading AL=F0h. The second LAHF is followed by CBW, which turns that into AX=FFF0h; XCHG then installs it as the stack pointer.

This is how to recover after intentional POPs and keep asynchronous interrupt pushes near the top of the segment. It discards the current stack, so it is safe only when no return address or saved value is live. The old SP is left in AX.

BunnySnowParty ๐Ÿฐโ˜ƒ๏ธ๐Ÿฐ๐Ÿ˜” - Safe addition: DI += 48h

This 18-byte sequence advances DI by 48h (72 decimal), absorbs the snowman's dangling POP, and repairs the stack:

0000: F0 9F       lahf
0002: 90          nop
0003: B0 E2       mov al,E2h
0005: 98          cbw
0006: 83 EF B8    sub di,FFB8h
0009: 8F F0       pop ax
000B: 9F          lahf
000C: 90          nop
000D: B0 F0       mov al,F0h
000F: 9F          lahf
0010: 98          cbw
0011: 94          xchg sp,ax

The first rabbit consumes the snowman's E2h; B8h is a signed -72 immediate, so SUB DI,-72 adds 72. The snowman's final 8Fh consumes the next rabbit's F0h as the undocumented POP AX encoding 8F F0. The final rabbit/sad-face pair restores SP=FFF0h.

This was one of the first gadgets found. The eye and plate gadgets are usually more efficient, but this one is included for completeness.

PlateGoat: ๐Ÿฝ๏ธ๐Ÿ - Increment SP and advance DI

The plate needs a following byte to complete its final 8Fh. A padding goat is a convenient harmless tail.

0000: F0 9F          lahf
0002: 8D BD EF B8    lea di,[di-4711h]
0006: 8F F0          pop ax
0008: 9F             lahf
0009: 90             nop
000A: 90             nop

B8EFh is the 16-bit representation of -4711h, so the LEA performs DI += B8EFh. The plate's 8Fh consumes the goat's leading F0h as undocumented POP AX; the remaining 9F 90 90 is LAHF; NOP; NOP.

Plates advance SP by two as well as moving DI a significant distance. Plates are excellent for pointer arithmetic, but their stack effects must be accounted for.

CatGoat: ๐Ÿ˜ฟ๐Ÿ - Absolute DI reset

The crying cat opens MOV DI,imm16. A padding goat supplies the immediate.

0000: F0 9F       lahf
0002: 98          cbw
0003: BF F0 9F    mov di,9FF0h
0006: 90          nop
0007: 90          nop

This eight-byte gadget resets DI to 9FF0h without depending on its previous value. If another emoji replaces the goat, its leading F0 9F still becomes the immediate, but its remaining bytes execute as a tail and may further change DI.

CamelWrite: ๐Ÿ—“๏ธ + arithmetic + ๐Ÿ“—๐Ÿช - Generate and write a byte

The generic form is 15 fixed bytes plus the chosen arithmetic sequence. Here is the simplest concrete example, ๐Ÿ—“๏ธ๐Ÿฎ๐Ÿ“—๐Ÿช, using a cow to add one:

0000: F0 9F       lahf
0002: 97          xchg di,ax
0003: 93          xchg bx,ax
0004: EF          out dx,ax      ; spurious OUT to B8EFh
0005: B8 8F F0    mov ax,F08Fh
0008: 9F          lahf
0009: 90          nop
000A: AE          scasb
000B: F0 9F       lahf
000D: 93          xchg bx,ax
000E: 97          xchg di,ax
000F: F0 9F       lahf
0011: 90          nop
0012: AA          stosb

On entry, DI is the real output pointer and AX contains the arithmetic seed. ๐Ÿ—“๏ธ moves the seed into DI and saves the output pointer in BX.

The middle emoji sequence performs arithmetic on this temporary DI; the cow above adds one.

๐Ÿ“— restores the output pointer and transfers the computed low byte into AL, and ๐Ÿช writes it.

The calendar also performs OUT DX,AX. The common startup establishes DX=B8EFh, a generally unmapped port. Use with caution on real hardware!

FrogStrap: ๐Ÿธโ˜บ๏ธ๐Ÿ˜–๐Ÿฐ๐Ÿ˜—๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ - Common register bootstrap

This 50-byte sequence obtains a known zero from the normal COM entry stack and establishes a specific register state:

0000: F0 9F       lahf
0002: 90          nop
0003: B8 E2 98    mov ax,98E2h
0006: BA EF B8    mov dx,B8EFh
0009: 8F F0       pop ax
000B: 9F          lahf
000C: 98          cbw
000D: 96          xchg si,ax
000E: F0 9F       lahf
0010: 90          nop
0011: B0 F0       mov al,F0h
0013: 9F          lahf
0014: 98          cbw
0015: 97          xchg di,ax
0016: F0 9F       lahf
0018: 98          cbw
0019: AF          scasw
001A: F0 9F       lahf
001C: 98          cbw
001D: AF          scasw
001E: F0 9F       lahf
0020: 98          cbw
0021: AF          scasw
0022: F0 9F       lahf
0024: 98          cbw
0025: AF          scasw
0026: F0 9F       lahf
0028: 98          cbw
0029: AF          scasw
002A: F0 9F       lahf
002C: 98          cbw
002D: AF          scasw
002E: F0 9F       lahf
0030: 98          cbw
0031: AF          scasw

๐Ÿธโ˜บ๏ธ loads DX=B8EFh and leaves a dangling 8Fh. The first byte of ๐Ÿ˜– completes POP AX, obtaining the known zero at SS:FFFEh and wrapping SP to 0000h; the rest of ๐Ÿ˜– moves that zero into SI.

๐Ÿฐ consumes the leading F0h from ๐Ÿ˜— to make AL=F0h, after which CBW; XCHG DI,AX establishes DI=FFF0h. Seven SCASWs advance it to FFFEh.

The final state is DX=B8EFh, SI=0000h, DI=FFFEh, and SP=0000h.

The value of B8EFh in DX is within the lower portion of CGA video memory if used as a segment.

An additional ๐Ÿ˜ฏ can roll DI over to 0000h. The value FFFEh left in DI has conceivable uses as a -2 constant.

๐Ÿธโ˜บ๏ธ๐Ÿฐ๐ŸŽโ™ - Load the CGA segment

This 21-byte sequence exploits the original 8088's undocumented segment-register alias to establish ES=B8EFh:

0000: F0 9F       lahf
0002: 90          nop
0003: B8 E2 98    mov ax,98E2h
0006: BA EF B8    mov dx,B8EFh
0009: 8F F0       pop ax
000B: 9F          lahf
000C: 90          nop
000D: B0 F0       mov al,F0h
000F: 9F          lahf
0010: 90          nop
0011: 8E E2       mov es,dx
0013: 99          cwd
0014: 90          nop

A Basic Loader

Frankly, writing directly in emojissembly is a miserable, painful slog, lacking reasonable immediate values, arithmetic operations, or sane jump offsets. Therefore it is sensible to construct a loader that can simply build 8088 code in memory and jump to it.

The trick is how to encode arbitrary byte data in emoji. With fewer than 4000 graphemes, emoji are hardly a 16-bit LUT. In fact, all 256 8-bit values do not occur in the 4-byte grapheme set in either the third or fourth byte position, however, by dumb luck, we can construct an 8-bit look-up table from the 2-byte tail of a set of 256 unique emoji.

Here's the basic idea:
bits 16
org 0

    push di
    pop  si                         ; SI = encoded emoji data
    mov  di,0100h
    push di                         ; RET target after reconstruction
    mov  cx,RAW_SIZE

.decode:
    lodsw                           ; discard the fixed F0 9F prefix
    lodsw                           ; AL=third UTF-8 byte, AH=fourth
    aad  8Fh                        ; AL=(AL + 8Fh*AH) & FFh; AH=0
    stosb
    loop .decode
    ret                             ; execute reconstructed COM at 0100h


The first lodsw reads the two-byte prefix of each 'data grapheme', discarding it. The second lodswthen reads the 16-bit tail, which is converted to an 8-bit value with the magic constant 8Fh. The key here is a feature of 8088's aadinstruction that allows it to take a non-decimal base as an immediate.

Of course, none of this works if lahf is constantly clobbering AH, so this 17-byte decoder must itself first be constructed in memory. This can be accomplished with the CamelWrite gadget described previously.

The downside here is that the encoded data is only 25% efficient, but anything else would significantly increase the complexity of the decoder and thus the complexity of the code that emits it.

A VGA Emoji Demo

Can we make a basic VGA demo with nothing but emoji? With our emoji-decoder, it's fairly straightforward to pack up a basic VGA demo, decode it into memory and jump to it. 

Here's the entire program:

๐Ÿธโ˜บ๏ธ๐Ÿ˜–๐Ÿฐ๐Ÿ˜—๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜ฏ๐Ÿ˜น๐Ÿ˜ฏ๐Ÿ•ณ๏ธ๐Ÿ˜ฏ๐Ÿฎ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿโญ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฎ๐Ÿฐ๐Ÿ˜”๐Ÿ˜ฌ๐Ÿ˜ฌ๐Ÿ—“๏ธ๐Ÿฝ๏ธ๐Ÿ‘๏ธ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿงฎ๐Ÿงฏ๐Ÿ“—๐Ÿช๐Ÿฎ๐Ÿ—“๏ธ๐Ÿ˜ฟ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿฎ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿงฎ๐Ÿงฎ๐Ÿงฏ๐Ÿงฏ๐Ÿ‘๏ธ๐Ÿ“—๐Ÿช๐Ÿฎ๐Ÿ—“๏ธ๐Ÿงฎ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿ˜ฟ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿงฏ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿ˜ฟ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฎ๐Ÿ“—๐Ÿช๐Ÿฎ๐Ÿ—“๏ธ๐Ÿ˜ฏ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ‘๏ธ๐Ÿ“—๐Ÿช๐Ÿฎ๐Ÿฎ๐Ÿฎ๐Ÿ—“๏ธ๐Ÿ˜ฟ๐Ÿงฎ๐Ÿงฎ๐Ÿงฎ๐Ÿ“—๐Ÿช๐Ÿ—“๏ธ๐Ÿงฎ๐Ÿ‘๏ธ๐Ÿ‘๏ธ๐Ÿฝ๏ธ๐Ÿ‘๏ธ๐Ÿ“—๐Ÿช๐Ÿฐ๐Ÿ˜”๐Ÿ๐Ÿโ™ฟ๏ธ๐Ÿ‡น๐Ÿ‡ญโช๏ธโœ‹๏ธ๐Ÿ’•๐Ÿ€๐Ÿฅ๐Ÿ๐Ÿ“ฃ๐Ÿˆš๏ธ๐ŸŒŒ๐ŸŒ‹๐Ÿ๐ŸŒ•๏ธ๐Ÿ‘ป๐ŸŒบ๐ŸŒป๐Ÿต๐Ÿ†™๐ŸŒฎ๐Ÿ๐ŸŒ๐ŸŒ‹๐Ÿ ๐ŸŒ‹๐Ÿ…๐Ÿœ๐Ÿ’’๐ŸŒณ๐Ÿ‘น๐ŸŽ‡๐Ÿ‘น๐Ÿ‰‘๐ŸŽŽ๐Ÿ‰‘๐Ÿ’ž๐Ÿ†–๐ŸŒณ๐Ÿ†Ž๐ŸŽ‡๐Ÿ‘น๐Ÿ‰‘๐ŸŽŽ๐Ÿ‰‘๐Ÿ’ž๐Ÿ–๐Ÿ‘ฏ๐Ÿ†–๐ŸŒป๐Ÿ••๏ธ๐ŸŒฟ๐Ÿ๐Ÿ™๐Ÿ‘บ๐Ÿ‘ฝ๏ธ๐ŸŒณ๐Ÿ‘น๐ŸŽ‡๐Ÿ’—๐Ÿ‰‘๐ŸŽŽ๐Ÿ‰‘๐Ÿ’ž๐Ÿ–•๐ŸŽŽ๐ŸŒ™๐Ÿ‘ป๐ŸŒบ๐ŸŒป๐Ÿป๐ŸŽ‚๐ŸŒป๐ŸŒณ๐Ÿ‘ฎ๐Ÿ–๐Ÿธ๐Ÿค๐Ÿ••๏ธ๐ŸŒป๐Ÿ ๐Ÿป๐Ÿ๐ŸŒป๐ŸŒณ๐ŸŒท๐ŸŽ๐ŸŒท๐Ÿญ๐ŸŒŒ๐Ÿ’—๐Ÿ‘ป๐Ÿ–๐Ÿ–๐ŸŒœ๏ธ๐ŸŒ›๐Ÿ“›๐Ÿ•๐Ÿ“๐Ÿช๐Ÿท๐ŸŒ™๐Ÿ˜๐Ÿพ๐Ÿช๐Ÿ‘ป๐ŸŒบ๐ŸŒป๐ŸŒ™๐ŸŒ˜๐ŸŒ™๐Ÿ—๐ŸŒŠ๐Ÿ๐Ÿ’—๐ŸŽ๐Ÿงฏ๐Ÿ”ฅ๐ŸŒ‰

Saved as a COM file, the file size should be 1092 bytes with an MD5 sum of 9ed65cc927b257b113a298dee37215cc.  The full disassembly can be viewed here

This program will run in any version of DOSBox.

I don't want to spoil it for you if you'd rather run it for yourself. If you'd rather take my word for it and see the result, click the spoiler below.

Spoiler โ€” click to reveal




I think I've proved the concept to my satisfaction - what remains to be done would be to create a more efficient packer/decoder, perhaps using LZ4 compression.  A COM2EMOJI utility would be fairly straightforward, and perhaps even EXE2EMOJI. 

Could you construct an emoji BIOS? An entire emoji operating system? Could we have EmojiDOOM?

I look forward to seeing whatever emoji-based horrors this post brings upon the world.

Read the whole story
jepler
4 days ago
reply
best kind of nerding
Earth, Sol system, Western spiral arm
Share this story
Delete

Celebrating 45 Years of Kermit with the First New C-Kermit Release in 15 Years (and working with a decades-old C codebase)

1 Share

1981 was a different time for computing. It was expensive (both hardware and software), and it was far from a given that machines from one vendor would be able to talk to those from another. In fact, Columbia University had just such a problem, so in 1981, Frank da Cruz and Bill Catchings designed a serial protocol they called Kermit. Because of the many quirks of the DEC-20 and IBM mainframes, the Kermit protocol was highly adaptable from the start: able to handle systems that had trouble processing more than 96 bytes of data at once, able to transfer 8-bit files over 7-bit links, able to translate between character sets (ASCII and EBCDIC then; now also various Unicodes), and of course, handling of error-prone serial links.

Kermit spread rapidly; by 1982, Kermit had been ported to MS-DOS and Unix. Eventually, C-Kermit (an implementation of Kermit in C) became the flagship Kermit. It gained TCP support, an interactive CLI, a powerful scripting language (with features from the shell, Lisp, and expect), and optimizations for todayโ€™s high-speed links, such as jumbo packets, sliding windows, and streaming modes. Along the way, Kermit flew on the International Space Station, ran data collection from sensors during hurricanes, and many other uses including postal systems, Boeing 787 manufacturing, and more.

Today, I use it as a powerful ssh wrapper (letting me easily transfer files through multiple nested ssh, sudo, su, etc. commands), a BBS client, to exchange data with me HP 48GX calculator, and so on. Itโ€™s also used today to transmit firmware updates to embedded devices. And, of course, anyone that works with vintage systems is likely to use Kermit at some point.

It wouldnโ€™t be until the late 1990s that the TCP/IP stack was finally adopted by most OS vendors, establishing something of a common basis for communication. Of course, we assume this today. Though transferring large files between OSs (say, Linux, Windows, MacOS, Android, iPad, etc.) is still a challenge, even though they all speak TCP/IP! I find that the easiest way to get large files from two computers is to spin up Kermit (see ckwin for a Windows fork of C-Kermit) and just set up a TCP connection over the LAN. In fact, I added a new show interfaces command in C-Kermit 11, making it easy to see your systemโ€™s local IPs.

For most of its history, Columbiaโ€™s Kermit project was self-funded. Columbia charged for commercial use, which limited its inclusion in Linux distributions. In 2011, 30 years after its founding, Columbia canceled the Kermit Project and released C-Kermit as Open Source under a BSD license. Frank da Cruz, who had still been working with the Kermit project all those years, volunteered to continue maintaining Kermit outside Columbia, and continued development with alpha and beta releases through his retirement from the project in 2025.

I dive into this C codebase

As Debian maintainer of Kermit, I noticed some areas where it wasnโ€™t matching modern expectations. One area was, not surprising for a project of its age, security. Another area was that its character set or line-ending conversions are usually not desired now; we are used to byte-identical binary transfers, and the defaults caused confusion and even some rare instances of data corruption. So I started making a few patches last year.

Iโ€™ve worked with old C codebases before, such as Varnish. Iโ€™ve generally hated it. You usually find a mix of bad and terrible practices, unclear memory management, and so forth.

But Iโ€™ve been living in the C-Kermit codebase for a few months now, and I enjoy it. Yes, this thing is still designed to build on VMS, OS/2, and with compilers that havenโ€™t heard of ANSI โ€” and those that require modern practices. (That em-dash was mine; I knew how to use them before LLMs existed and Iโ€™m not going to stop just because LLMs have copied people like me! No AI was used for this post.)

The there is an elegance in all of that. As I worked, I fixed a bunch more potential security issues, both with memory safety and with protecting against a malicious remote in roughly the same manner that some patches to scp did a few years back. I added IPv6 support, of course conditionally compiled because some systems C-Kermit builds on have never heard of IPv6 and never will. (And, of course, with fallback algorithms at runtime for systems that have IPv6 support but not IPv6 connectivity.)

I added unit tests and Python-based end-to-end tests, running nearly 2000 test cases in total. Along the way, I found and fixed a number of bugs going back decades. I learned about FIONREAD being broken on macOS, about NetBSDโ€™s bugs in the pty driver, and fixed bugs in the Kermit protocol implementation itself. I added compatibility tests with the gkermit and ekermit (embedded) implementations, as well as the last full release, C-Kermit 9.0.302 from 2011 (which was difficult to get compiled on a modern system).

There is an extensive changelog describing all the improvements in C-Kermit 11.

C-Kermit development had never really used a VCS at any point, though Kermit veteran Jeffrey Altman imported historical releases into a Git repo, along with some patches that hadnโ€™t made it into a release (which I also pulled in.) There was a lot of disabled code behind COMMENT, along with commentary describing why it was no longer used. With Git, we would now generally just remove the old code and explain why in a commit message. I went through and did so with a lot of it, meaning that, at last check, C-Kermit actually has fewer lines of code now than it used to.

Towards a new release

It became apparent pretty quickly that I was making more changes than would make sense as a Debian patch series. Not only that, but they would be more widely applicable to more than just Debian and Ubuntu users. As Linux and BSD distributions were running everything from the last non-beta release (2011โ€™s 9.0.302) to the last beta release (about 1.5 years ago), depending on their different policies about running betas, even sharing patches in a useful fashion was going to be quite difficult.

So, I spun up a project at Open Kermit to coordinate future development in the open and keep Kermit going.

With modern CI, I run that test suite on Linux (x86_64 and arm64), macOS, FreeBSD, NetBSD, and OpenBSD. It builds binary releases on all those platforms, plus a statically-linked Linux binary built with musl libc.

You can download the latest C-Kermit release, and of course contribute to C-Kermit and its website.

Dedication

Frank da Cruz was directly involved with Kermit for 44 years. Iโ€™m not aware of any other Open Source project founder being involved for so long. Richard Stallman started working on GNU Emacs in 1984, 3 years after Frank started working on Kermit, but Richard hasnโ€™t been in that role since around 2008.

Accordingly, C-Kermit 11 bears this dedication:

I dedicate this release of C-Kermit to Frank da Cruz.

Frank was directly involved with Kermit for 44 years, from its initial design in 1981 all the way through 2025. He maintained Kermit as an Open Source project after Columbia University ended its sponsorship. I know of no other Open Source project where the founder remains so personally involved for so long.

When Kermit was begun, transfers between different hardware and operating systems were difficult or impossible. Frank helped build a bridge. Kermit glued systems together, from the International Space Station to pocket calculators, and set a new standard for interoperability. It continues to do so.

Kermit is still one of the quietly-working pillars of computing today, enabling everything from firmware upgrades to radios. And, yes, it still reliably transfers files over serial lines.

As we start to spend a lot of time in the Kermit codebase, we do so standing on the shoulders of a giant. Thanks, Frank, for your decades of work on Kermit.

John Goerzen, July 2026

Read the whole story
jepler
8 days ago
reply
Earth, Sol system, Western spiral arm
Share this story
Delete

NetBSD 11.0 released

1 Comment

The release of NetBSD 11.0, the 19th major version of the operating system, has been announced. There are many changes and enhancements since the 10.1 release, including a new port to RISC-V, better support for Linux system calls in compat_linux(), as well as improvements to the NPF firewall.

As you are probably aware, the number of security issues found or suspected everywhere has massively increased with the advent of AI tools. As a consequence, we can't publish a release without open issues. Instead of delaying the release further to fix them (new ones are being reported all the time), we've instead chosen to be transparent about this.

See the full release notes for links to the binary distributions and links to the full change logs.

Read the whole story
jepler
8 days ago
reply
I should try netbsd one of these days!
Earth, Sol system, Western spiral arm
Share this story
Delete

27jul2026

1 Comment
Read the whole story
jepler
14 days ago
reply
Project LANA is real interesting. The math is all far far above my head, but I think it represents something important: a collaborative effort among mathematicians to use formal computer-verified proofs to resolve disagreements among people who have carefully studied a proposed proof and find themselves in disagreement.

I don't know which way this particular question will be resolved (currently, LANA is finding that the Corollary 3.12 question raised by Scholze and Stix is proving hard to formalize but has not found a formalization that confirms or contradicts Mochizuki) -- but the meta-result of *whether* the formal methods will ultimately serve to drive a consensus in this case is what will be interesting to see. Personally, I hope LANA is successful at formalizing AND that their final formalization does become the point of consensus about IUT.

The other main possible outcomes:
* An "unaided" human outside LANA finds a persuasive argument not yet in the public record, which is then used by LANA to finish formalization. This would be great. For now, as I understand it, LANA is not sharing their repository of Lean code, so others can't yet directly contribute to the formalization.
* An "unaided" human outside LANA finds a persuasive argument that creates consensus. In this case LANA might find it is not sufficiently interesting to finish their formalization. This would be too bad, and not settle the meta-question.
* LANA never conclusively formalizes the model to the point where it offers a resolution to the Corollary 3.12 question. Unfortunate, and would point to limits at our ability to formalize with systems like Lean.
* LANA produces what they believe is a formal result, but still fail to achieve consensus in the community. Unfortunate, and would point to limits in formal methods at being persuasive to practicing mathematicians.
Earth, Sol system, Western spiral arm
Share this story
Delete
Next Page of Stories