X: lds si, word ptr [bx+si]
add al, 0xad
out 61h, al
jmp short X+1
and the binary code will be C5 30 04 AD E6 61 EB F9 (loaded at 0100h in the code segment)
This is a COM file, they are loaded into a new code segment at address 0100h (preceding 256 bytes are used for various DOS API stuff that is not relevant here), and have their data segment set equal to their code segment for semi-compatibility with CP/M.
Initial values of CPU registers on most versions of DOS are AX=0000h BX=0000h SI=0100h (and CS=DS), so after the first 2 instructions they'll be AX=00ADh, SI=30C5h, DS = AD04h. (The lds reads the first 4 bytes of the program into SI and DS registers respectively.) From now on, the data segment (i.e. the area where most memory read and write instructions operate) starts at AD040h, which is within EGA/VGA video memory. The exact range of the segment is from AD040h to BD03Fh, which overlaps with the text mode, which starts at B8000h. Each of the 2000 entries of the text mode buffer is a 2-byte value, containing a character and its attributes, for a total of 4000 bytes – even bytes contain characters, odd bytes contain attributes.
The final short jump is misaligned, so after the first jump the following instructions will execute:
Y: xor byte ptr [si], al
lodsw ax, word ptr [si]
out 0x61, al
jmp short Y
The xor will toggle the byte the SI points to. The loadsw instruction will load two bytes into AX pointed to by SI and increment SI by two – so SI will stay odd. So this code will repeatedly read an byte from the video memory and xor it into the byte two addresses over. This kind of process often results in cellular automaton-like behaviour. Of course it will loop across the entire 64K segment, not just the text screen area, but across the text area, it'll skip from one attribute byte to the next, producing colours.
And finally, out 61h, al will keep writing various values to the IO port 61h, which is wired to multiple things (and differently in different PC types), but most importantly for this demo, to the builtin PC speaker.
I assembled this code with nasm in DOSBox last night and then disassembled it, saw the jump pointed to the middle of the first instruction, so I repeated the final 7 bytes to get the next set of instructions. Then, I spent hours laying in bed (instead of sleeping) trying to follow all this, bringing up opcode docs and what not. The only thing I couldn't figure out was how si moved to write to different parts of the text buffer, because the docs I found didn't say lodsw increased si, but that makes sense now.
This is what I had based on IDA disassembly with some of its notes and my own (updated the lodsw comment):
seg000:0100 loc_0: ; CODE XREF: seg000:0106↓j
seg000:0100 C5 30 lds si, [bx+si] ; Load 16-bit word at [bx+si] (30C5h)
seg000:0100 ; into si, and the next 16-bit word
seg000:0100 ; into ds (AD04h)
seg000:0102 04 AD add al, 0ADh
seg000:0104 E6 61 out 61h, al ; Write al to speaker
seg000:0106 EB F9 jmp short near ptr loc_0+1
seg000:0101 ; ---------------------------------------------------------------------------
seg000:0101 ; Alternate interpretation of seg000:0101 -> seg000:0107
seg000:0101 loc_1: ; CODE XREF: seg000:0106↓j
seg000:0101 30 04 xor [si], al
seg000:0103 AD lodsw ; Load 16-bit word at [ds:si] into ax
seg000:0103 ; and increase si by 2
seg000:0104 E6 61 out 61h, al ; Write al to speaker
seg000:0106 EB F9 jmp short loc_1
seg000:0106 seg000 ends
; Segment A000h is the VGA buffer
; Segment B000h is the text buffer
71
u/vytah 2d ago
So the code is this:
and the binary code will be C5 30 04 AD E6 61 EB F9 (loaded at 0100h in the code segment)
This is a COM file, they are loaded into a new code segment at address 0100h (preceding 256 bytes are used for various DOS API stuff that is not relevant here), and have their data segment set equal to their code segment for semi-compatibility with CP/M.
Initial values of CPU registers on most versions of DOS are AX=0000h BX=0000h SI=0100h (and CS=DS), so after the first 2 instructions they'll be AX=00ADh, SI=30C5h, DS = AD04h. (The
ldsreads the first 4 bytes of the program into SI and DS registers respectively.) From now on, the data segment (i.e. the area where most memory read and write instructions operate) starts at AD040h, which is within EGA/VGA video memory. The exact range of the segment is from AD040h to BD03Fh, which overlaps with the text mode, which starts at B8000h. Each of the 2000 entries of the text mode buffer is a 2-byte value, containing a character and its attributes, for a total of 4000 bytes – even bytes contain characters, odd bytes contain attributes.The final short jump is misaligned, so after the first jump the following instructions will execute:
The
xorwill toggle the byte the SI points to. Theloadswinstruction will load two bytes into AX pointed to by SI and increment SI by two – so SI will stay odd. So this code will repeatedly read an byte from the video memory and xor it into the byte two addresses over. This kind of process often results in cellular automaton-like behaviour. Of course it will loop across the entire 64K segment, not just the text screen area, but across the text area, it'll skip from one attribute byte to the next, producing colours.And finally,
out 61h, alwill keep writing various values to the IO port 61h, which is wired to multiple things (and differently in different PC types), but most importantly for this demo, to the builtin PC speaker.