P$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nuzj X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j XHP$ g!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu Nujj XP4 g!E"Xg1E"%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuuj X4$Ph DXD4}P% g!E Xg1E j$XP4$g!E"Xg1E"%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j%XP4%g!E"Xg1E"j X4 P^Fu NuK%@@% Hg3E"@g)E j X,UP$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j&XP4&g!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,*P,~4xg!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu Nulj)XP4)g!E"Xg1E"%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nudj X4$Ph DXD4}P% g!E Xg1E j*XP4*g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nuyj X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j+XP4+g!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,-P,~4ug!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j,XP4,g!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NuZj0XP40g!E"Xg1E"%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j1XP41g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nuyj X,UP$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j2XP42g!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,*P,~4xg!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j5XP45g!E"Xg1E"j X4 P^Fu Nu]%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuuj X4$Ph DXD4}P% g!E Xg1E j6XP46g!E"Xg1E"%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j7XP47g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j8XP48g!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu Nujj XP4>g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,*P,~4xg!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jAXP$ g!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NuZjBXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jCXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jDXP$ g!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NujjHXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuuj X4$Ph DXD4}P% g!E Xg1E jIXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,UP$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jJXP$ g!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,*P,~4xg!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jMXP$ g!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NujjNXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nudj X4$Ph DXD4}P% g!E Xg1E jOXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jPXP$ g!E"Xg1E"j X4 P^Fu NuK%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jTXP$ g!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NuZjUXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,UP$ g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jVXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,*P,~4xg!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jYXP$ g!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jZXP$ g!E"Xg1E"j X4 P^Fu Nu[%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuwj X4$Ph DXD4}P% g!E Xg1E j[XP$ g!E"Xg1E"%@@% Hg3E"@g)E j X,-P,~4ug!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j\XP$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nuyj X48P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j`XP4`g!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NujjaXP4ag!E"Xg1E"%@@% Hg3E"@g)E j X,UP$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuwj X4$Ph DXD4}P% g!E Xg1E jbXP4bg!E"Xg1E"%@@% Hg3E"@g)E j X,*P,~4xg!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jeXP4eg!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,~4'P$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jfXP4fg!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NuljgXP4gg!E"Xg1E"%@@% Hg3E"@g)E j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuuj X4$Ph DXD4}P% g!E Xg1E jhXP4hg!E"Xg1E"%@@% Hg3E"@g)E j X48P$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jlXP4lg!E"Xg1E"j X4 P^Fu NuK%@@% Hg3E"@g)E j X,}P$@g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jmXP4mg!E"Xg1E"j X4 P^Fu Nu[%@@% Hg3E"@g)E j X,UP$ g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuwj X4$Ph DXD4}P% g!E Xg1E jnXP4ng!E"Xg1E"%@@% Hg3E"@g)E j X,*P,~4xg!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jqXP4qg!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,~4'P$ g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jrXP4rg!E"Xg1E"j X4 P^Fu Nuz%@@% Hg3E"@g)E j X,fP$@g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu NuljsXP4sg!E"Xg1E"%@@% Hg3E"@g)E j X,-P,~4ug!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nudj X4$Ph DXD4}P% g!E Xg1E jtXP4tg!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E juXP4ug!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jvXP4vg!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu Nu\jwXP4wg!E"Xg1E"%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jxXP4xg!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jyXP4yg!E"Xg1E"j X4 P^Fu NuM%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P^Fu Nuhj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E jzXP4zg!E"Xg1E"j X4 P^Fu Nu]%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nuwj X4$Ph DXD4}P% g!E Xg1E j{XP4{g!E"Xg1E"%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P^Fu Nuxj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j|XP4|g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nu{j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j}XP4}g!E"Xg1E"j X4 P^Fu Nu|%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j X4 P^Fu Nulj~XP4~g!E"Xg1E"%@@% Hg3E"@g)E j X,!P$~4~g!E"Xg1E"j X4 P_PZg3} 5g2u"05P^Fu Nujj X4$Ph DXD4}P% g!E Xg1E j~X@P4~Hg!E"Xg1E"%@@% Hg3E"@g)E j X4 g!E"P^Fu Nuzj X4 P_PZg3} 5g2u"05,~,~Ph DXD4}P% g!E Xg1E j~X@@P$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 P^Fu Nuzj X4 g!E"P_PZg3} 5g2u"05@@Ph DXD,)Pj X4 g!E Xg1E j X4!Ph DXD4EPj X4 g!E"Xg1E"j X4 P^Fu Nuk%@@% Hg3E"@g)E j X4 g!E"P_PZg3} 5g2u"05@@Ph DXD,~P%@ g!E Xg1E j X4 P^Fu Nu]jTXP$ g!E"Xg1E"%@@% Hg3E"@g)E j X4 g!E"P_PZg3} 5g2u"05P^Fu Nutj X4!Ph DXD4gP% g!E Xg1E j X4!Ph DXD4zP% g!E"Xg1E"%@@% Hg3E"@g)E j X4 g!E"P^Fu Nuej X4 P_PZg3} 5g2u"05@Ph DXD4,P% g!E Xg1E j:XP4:g!E"Xg1E"j X4 P^Fu Nu{%@@% Hg3E"@g)E j X4 g!E"P_PZg3} 5g2u"05@Ph DXD4$P% g!E Xg1E j X4'P$ g!E"Xg1E"j X4 P^Fu NuR%@@% Hg3E"@g)E j X4 g!E"P_PZg3} 5g2u"05CCCCP^Fu NuUj X,~44P$ g!E Xg1E j X4 g3E PEEEEj X4!Ph DXD,uP^4Uq NuXMMMMj X4 g!E"g1]",~4}Ph DXD,@g)E"j X4 P_g3}"3=g1}"P^Fu NuzKKKKj X4 g!E g1] ,~4}Ph DXD,@g)E j X4 P^P_g3u ! P$ g!E Xg1E j X4 g3E PEEEEEEj X4"PLXD$@$ 4$P^4Uq NuGMMMMMMKKKKKKj X,oP$@g!E"Xg1E"j X4 P^Fu Nubj X4 g!E$CCCCCCg!E g1] ,~4}Ph DXD,Bg)E j X4 P^P_g3u ! P^4Uq Nugj X4 g!E g1] ,~4}Ph DXD,Hg)E j X4 P_g3} 3=g1} 40P40g!E"Xg1E"j X4 P^Fu Nuej X4 g3E"g)E j X4 g!E"g1]",~4}Ph DXD,Lg)E"j X4 P^P_g3u"! P^4>g3E u*j X4 P^Fu Nu\j X4 g!E g1] ,~4}Ph DXD,Xg)E j X4 g!E"P^P_g3u ! P% g!E"Xg1E"j X4 P^Fu NuNCCCCCCj X4 g!E$g1]$,~4}Ph DXD,Bg)E$j X4 P^Fu Nuhj X4 P^P_g3u$! j X4 g!E$P^P_g3u ! XX \xFF >>>>>>" .. Speaking of running the program, old-style EXE files no longer run on "????????????????\0"; .. 64-bit versions of Windows. So if you do not have an old DOS computer unsigned char *lower = "\xA9\xB3\xBD\xC9\xD5\xE1\xEF\xFD\x0C\x1C-?Qf{" .. around with a sound card, you can run ABC-compiled programs inside an "\x91\xA9\xC2\xDD\xFA\x18" "8Y}\xA3\xCB\xF6#R\x85\xBA\xF3\x18" .. emulator. DOSBox is an excellent choice. It runs on pretty much all "8Y}\xA3\xCB\xF6#R\x85\xBA\xF3\x18" "8Y}\xA3\xCB\xF6#R\x85\xBA" .. platforms (well, it doesn't run on DOS, but on DOS you can just use "\xF3\x18" "8Y}\xA3\xCB\xF6#R\x85\xBA\xF3\x18" "8Y}\xA3\xCB\xF6#R" .. DOS) and tends to just work. You have to do something like "\x85\xBA\xF3\x18" "8Y}\xA3\xCB\xF6#R\x85\xBA\xF3\x18" "8Y}\xA3\xCB" .. "\xF6#R\x85\xBA\xF3\x18" "8Y}\xA3\xCB\xF6#R\x85\xBA\xF3\xFF\xFF" .. MOUNT C C:\DOWNLOADS\ABC\ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0"; .. .. in order to mount one of your real directories as a "hard drive". To unsigned char *default_song = .. verify that PAPER.EXE is printable with no beeping or funny "abd'b^f'3^f'3e'6ab^c'ae'3e'3d'^c'b4ab^c'ad'4e'2^c'2b2a2z2a2e'4d'4z" .. characters, you could do "4abd'b^f'3^f'3e'6ab^c'aa'4^c'2d'2^c'b3ab^c'ad'4e'2^c'3ba2z2a2e'2d'2d'4|" .. "A4E4E4A4A4^F4^F4B4B4E4E4A4A4^F4B4A4A4E4E4A4A4^F4^F4B4B4E4E4A4A4^F4B4A2" .. COPY PAPER.EXE CON "A2A2A2|A,2^F,2E,6A,4E2^F2E2^F,6B,2A,2B,2A,2^F,2E,2^F,2G,2A,4E2^F2A2^F,6" .. "A,4E2^F2A2E,2E,2^F,2A,4E2^F2E2^F,6B,4^F2A2^F2E,2^F,2G,2A,4E2^F2E2^F,6A," .. to copy it to your console, or COPY PAPER.EXE LPT1 to copy it to your "4E2^F2A2"; .. simulated computer's printer (spoiler: It doesn't have one). But why .. bother? You're reading PAPER.EXE right now! unsigned char *alphabet = .. "C4C4G4G4A4A4G8" "F4F4E4E4D4D4C8" .. I used DOSBox frequently during development, and modified its "G4G4F4F4E4E4D8" "G4G4F4F4E4E4D8" .. debugger, especially for understanding the header values are actually "C4C4G4G4A4A4G8" "F4F4E4E4D4D4C8"; .. used. The ABC compiler outputs each of the intermediate languages for .. a program as it compiles, as well as lightly-commented X86 assembly unsigned char *plumber = .. with address maps back into the code segment, which makes it possible "e'e'ze'zc'e'zg'z3g2z2c'z2gz2ez2azbz^aazge'zg'a'2f'g'ze'zc'd'bz2c'z2gz" .. to easily set breakpoints on particular pieces of code. Since "2ez2azbz^aazge'zg'a'zf'g'ze'zc'd'bz4g'^f'f'^d'ze'z^gac'zac'd'z2g'^f'f'" .. compiling other people's software on Windows is a special nightmare, I "^d'ze'zc''zc''c''z5g'^f'f'^d'ze'z^gac'zac'd'z2^d'z2d'z2c'|" .. frequently worked inside a Linux virtual machine (VirtualBox) "DDzDzDDzgz3G2z2Gz2Ez2Cz2FzGz^FFzEczef2dezczABGz2Gz2Ez2Cz2FzGz^FFzEczef" .. containing a DOS virtual machine (DOSBox), a surreal scenario that I "zdezczABGz2Cz2Gz2czFz2cczFzCz2Ez2Gczg'zg'g'zGzCz2Gz2czFz2cczFzCz^Gz2^A" .. was tickled to find a practical use for. Let us one day simulate "z2cz2GGzC"; .. Windows 7 on our iPhones 21 so that we may render this development .. environment one level deeper. unsigned char *bluehair = .. "^A8z2F4^G8F3c4^A4F4^A4^G8z8" .. My modifications to DOSBox are included in the ABC source repository, "^A8z2c8z2^c8z2^d8z2f8z2F4F4F4F8"; .. although they are not necessary to run ABC-compiled programs. When .. running these programs under DOSBox with the debugger enabled, it will typedef struct { .. complain about a "weird header" when loading the program (you're unsigned char *song; .. tellin' me!) and the debugger will output the error int idx; .. int midi_note; .. Illegal/Unhandled opcode 63 unsigned int ticksleft; .. } Channel; .. upon exiting (because we do execute an illegal opcode). For cosmetic .. style points, the local version of DOSBox has been modified to instead int Adlib(int reg, int value) { .. output int i; .. _out8((int)0x0388, (int)reg); .. // We have to wait "12 cycles" after writing the port. .. Thank you for playing Wing Commander! for (i = 0; i < (int)12; i++) {} .. _out8((int)0x0389, (int)value); .. // And 84 cycles after writing the value. These numbers are .. // probably far too high; recall that a for loop like this .. ** 29. PAPER.C ** // has to jump through every rung in the program! (i.e., .. // A single iteration is linear in the program size.) .. This section contains the C source code that was compiled into this for (i = 0; i < (int)84; i++) {} .. paper. It may be interesting to see how the code (e.g. string return 0; .. literals) make their way into the data for the paper. You may also } .. laugh at my many troubles: .. int PlayNote(int ch, int midi_note) { .. - I'm playing music, which has some dependency on timing, but there // First turn note off; silence is better than weird "accidentals." .. is no way to get access to the system clock. Instead, I use for Adlib((int)0xB0 + ch, 0x00); .. loops with built-in constants determined empirically. At least // midi_note = 128 actually accesses the terminating \0 in the .. this technique of relying on the CPU's cycle timing for delays // above strings, which is what we want to turn off the channel. .. was common in the DOS era, so this is, like, a period piece. Adlib((int)0xA0 + ch, (int)(lower[midi_note])); .. Adlib((int)0xB0 + ch, (int)(upper[midi_note])); .. - However, since the routine that calculates lengths performs a } .. multiplication, and multiplication of m * n is O(n), the delays .. are not actually linear. // Zero all the adlib ports, which both silences it and .. // initializes it. .. - You can see the many places where I'm applying explicit casts, int Quiet() { .. either because an implicit coercion is not yet implemented for int port; .. ABC (I want to do it right, and the rules are a little subtle), .. because some operation is not yet available at char or long type // Clear the main tones first, so that we don't hear artifacts during .. (I implemented 16-bit first), or for efficiency. // the clearing process if a note is playing. .. Adlib((int)0xB0, (int)0x00); .. - You can see the reliance on string literals for efficient lookup Adlib((int)0xB1, (int)0x00); .. tables, in keeping with the "printable" theme. Adlib((int)0xB2, (int)0x00); .. .+..............................................................................................................................................................++..............................................................................................................................................................+. .. .. default: .. for (port = (int)0x01; port <= (int)0xF5; port++) { if (c >= (int)'A' && c <= (int)'G') { .. Adlib((int)port, (int)0x00); midi_note = ParseNote(ptr, c, idx) + sharpflat; .. } *len = ParseLength(ptr, idx); .. } return midi_note; .. } else if (c >= (int)'a' && c <= (int)'g') { .. // ABC provides no standard library, so you gotta roll midi_note = ParseNote(ptr, c - (int)32, idx) + (int)12 + sharpflat; .. // your own. *len = ParseLength(ptr, idx); .. int strlen(unsigned char *s) { return midi_note; .. int len = 0; } .. while ((int)*s != (int)0) { } .. len++; } .. s = (unsigned char *)((int)s + (int)1); } .. } .. return len; // Adlib has 9 channels, but they are packed in groups of .. } // three (c1o1, c2o1, c3o1, c1o2, c2o2, c3o2, c4o1, ...). .. // So this only works for the first three channels. Not .. int streq(unsigned char *a, unsigned char *b) { // too hard to generalize, especially with a table. .. int i; int InitInstrument(int ch) { .. for (i = 0; /* in loop */; i++) { // Initialize the Adlib instrument. .. int ca = a[i], cb = b[i]; Adlib((int)0x20 + ch, 0x01); // Modulator multiple 1. .. if (ca != cb) return (int)0; Adlib((int)0x40 + ch, 0x10); // Modulator gain ~ 40db. .. if (ca == (int)0) return (int)1; Adlib((int)0x60 + ch, 0xF0); // Modulator attack: quick. Decay: long. .. } Adlib((int)0x80 + ch, 0x77); // Modulator sustain: med. Release: med. .. } Adlib((int)0x23 + ch, 0x01); // Carrier multiple to 1. .. Adlib((int)0x43 + ch, 0x00); // Carrier at max volume. .. // DOS command lines always start with a space, which is annoying. Adlib((int)0x63 + ch, 0xF0); // Carrier attack: quick. Decay: long. .. // Strip that. DOS also terminates the command line with 0x0D, not Adlib((int)0x83 + ch, 0x77); // Carrier sustain: med. release: med. .. // 0x00. This function updates it in place so that we can use normal } .. // string routines on it. .. int MakeArgString(unsigned char **argstring) { // First test for known songs. After that, if we have a command line, .. unsigned char *s = *argstring; // use it. Otherwise, use the default song. .. while (*s == (int)' ') { unsigned char *GetSong(unsigned char *cmdline) { .. s = (unsigned char *)((int)s + (int)1); if (streq(cmdline, (unsigned char *)"-alphabet")) { .. } return alphabet; .. *argstring = s; } else if (streq(cmdline, (unsigned char *)"-plumber")) { .. return plumber; .. while ((int)*s != (int)0x0D) { } else if (streq(cmdline, (unsigned char *)"-bluehair")) { .. s = (unsigned char *)((int)s + (int)1); return bluehair; .. } } else if (strlen(cmdline) > (int)0) { .. *s = (unsigned char)0; return cmdline; .. return 0; } else { .. } return default_song; .. } .. // We pick octave 4 as the base one; this is fairly canonical and } .. // benefits us since this array is all printable. Note that A4 is .. // higher than C4, since octave 4 begins at the note C4. This // Note: Doesn't check that the input is within the maximum number of .. // array maps A...G to the corresponding MIDI note. // channels! .. unsigned char *octave4 = int SplitChannels(unsigned char *song, Channel *channels) { .. "9" // A = 57 int i, current_channel = 0; .. ";" // B = 59 unsigned char *prevsong = song; .. "0" // C = 48 for (i = (int)0; /* in loop */; i++) { .. "2" // D = 50 int c = song[i]; .. "4" // E = 52 switch (c) { .. "5" // F = 53 case '|': .. "7"; // G = 55 case '\0': { .. // Parse a character c (must be capital A,B,C,D,E,F,G) Channel *channel = &channels[current_channel]; .. // and interpret any suffixes as well. channel->song = prevsong; .. int ParseNote(unsigned char *ptr, int c, int *idx) { // Silence; ready for next note. .. int midi; channel->midi_note = (int)128; .. int offset = c - (int)'A'; channel->ticksleft = (int)0; .. int nextc; channel->idx = (int)0; .. midi = octave4[offset]; .. for (;;) { song[i] = (unsigned char)'\0'; .. nextc = (int)ptr[*idx]; current_channel++; .. switch (nextc) { // Start after the nul-terminator byte. .. case '\'': prevsong = &song[i + (int)1]; .. // Up octave. if (c == (int)0) return current_channel; .. midi += (int)12; } .. break; } .. case ',': } .. // Down octave. } .. midi -= (int)12; .. break; int main(int argc, unsigned char **argv) { .. default: Channel channels[3]; .. // Not suffix, so we're done (and don't consume unsigned char *song, *cmdline = *argv; .. // the character.) int i, num_channels; .. return midi; .. } MakeArgString(&cmdline); .. *idx = *idx + (int)1; song = GetSong(cmdline); .. } .. } // Initialize channels. Note that this will just blow the .. // stack-allocated channels array if there are more than .. unsigned int ParseLength(unsigned char *ptr, int *idx) { // two in the input string! .. int c = (int)ptr[*idx]; num_channels = SplitChannels(song, (Channel *)&channels); .. if (c >= (int)'2' && c <= (int)'8') { .. int m = c - (int)'0'; Quiet(); .. *idx = *idx + (int)1; .. return (unsigned int)200 * m; for (i = 0; i < num_channels; i++) .. } InitInstrument(i); .. return (unsigned int)200; .. } for (;;) { .. int ch, all_done = 1; .. // Parse the song description (ptr) starting at *idx. Updates *idx to // At each tick (whose rate is governed just by the time .. // point after the parsed note. Updates *len to be the length in some // it takes to do this loop), reduce each channel's ticksleft; .. // unspecified for-loop unit. Returns the MIDI note to play next, or 0 // if it was (already) zero, load a new note. .. // when the song is done. for (ch = 0; ch < num_channels; ch++) { .. int GetMidi(unsigned char *ptr, int *idx, unsigned int *len) { Channel *channel = &channels[ch]; .. int c, midi_note; int midi_note = channel->midi_note; .. int sharpflat = 0; if (midi_note != (int)0) { .. for (;;) { int ticksleft = channel->ticksleft; .. c = (int)(ptr[*idx]); all_done = 0; .. if (ticksleft > (int)0) { .. // End of string literal. channel->ticksleft = ticksleft - (int)1; .. if (c == (int)0) return 0; } else { .. int new_note = GetMidi(channel->song, &channel->idx, .. // Advance to next character. &channel->ticksleft); .. *idx = *idx + (int)1; channel->midi_note = new_note; .. if (new_note == (int)0) { .. switch (c) { // Quiet the channel -- forever! .. case '^': PlayNote(ch, (int)128); .. sharpflat++; } else { .. break; PlayNote(ch, new_note); .. case '_': } .. sharpflat--; } .. break; } .. case '=': } .. // Nothing. We assume key of C, so there are no naturals. if (all_done) break; .. break; } .. case 'z': .. *len = ParseLength(ptr, idx); Quiet(); .. // No sound. return 0; .. return 128; } .. .+..............................................................................................................................................................++..............................................................................................................................................................+. .. .. encoding. So what's the big deal? .. ** 30. Is this useful for anything? ** .. .. No. This is a SIGBOVIK paper. <3 ## ## .. ### #### .. ### ### .. ** 31. Future work ** ### .. ##### ############ .. There are many code size optimizations possible, and while nontrivial ### ### ##### ######### .. programs can fit in 64k (such as the one in this paper), larger ones ## ### ### ######### .. will run up against that boundary quickly. Probably a factor of about ### ##### .. 4 can be gained through a few hard but straightforward optimizations. ##### ## ##%## .. Can we break free of the 64k boundary? Earlier we noted that when ######## ### =### ## ##%%### .. execution exceeds CS:0xFFFF, it simply continues to CS:0x00010000 ### ### ## ### ##%%### .. unless a jump is executed across that boundary; this address is ## ## ### ## ##%%%## .. pointing to bytes that are part of our program image (this text is ## ### ######## ## ##%### .. there, in fact), so conceivably we could write code here. One =#######= ##= ## ## ##%%## .. significant issue is that interrupts, which are constantly firing, ### ### ## ## ## ##%%### .. push 16-bit versions of CS and IP onto the stack, and then RETF ## ### ######## ## ##%%## .. (return far) to that address. This means that if an interrupt happens ## ### #### ## ##%%## .. while we are executing in this extended address space, we will return ####### ######## ##%%## .. to CS:(EIP & 0xFFFF). If we had control over interrupts, this might be ###%%#### ######## .. a good way to return to the normal 16-bit code segment (i.e., to ########################### ####%%%## .. perform a backwards jump), but as discussed, we do not. We may be able ####-----------##%%%%%%%%%%%%##################%%# .. to globally suppress interrupts, like by using our single illegal ###-------------##%%%%%%%%%%%%%%%%%%%%%%%%%##--################ .. instruction interrupt during initialization, with the interrupt ###--------------###%%%%%%%%%%%%%%%%%%%%%%%###--------------##### .. handler pointing just to code that we control (and never returning ##-----------------###%%%%%%%%%%%%%%%%%%%%####-------------###%%## .. from it). This leaves the interrupt flag cleared, as discussed. The ##--------------------#####%%%%%%%%%%%#######--------------###%%%## .. computer will be non-functional in many ways, because the operating ###-----------------------##############-------------------###%%%%%## .. system will no longer run, but we might still be able to do ##====----------------------------------------------------###%%%%%%## .. rudimentary port-based I/O, or build our own non-interrupt-based OS. ###=========-----------------------------------------------##%%%%%%%## .. With interrupts suppressed, we can't use the interrupt trick to return ##===============------------------------------------=====##%%%%%%%%## .. to CS:0000. However, my reading of the Intel manual [INTC] seems to ##============================--------------==============##%%%%%%%%## .. imply that a jump performed from this region can be forced into 16-bit ###=======================================================##%%%%%%%%## .. mode (thus being subject to the & 0xFFFF overflow) with an address ###=======================================================##%%%%%%%%## .. size prefix; however, this does not seem to be the case in DOSBox. ##=======================================================##%%%%%%%%%## .. Given how unusual this situation is, it may even be a bug in DOSBox's ##=============================================%%###=====##%%%%%%%%### .. CPU emulator. Having access to a full megabyte of code (it still needs ##=============================================## ##===##%%%%%%%### .. to fit in the EXE container) would be exciting, since it would allow ##============================================%%#####%%==##%%%%%%###--- .. us to build much more significant systems (e.g. standard malloc and a #####========#########=================================##%%%%%##----- .. floating point emulator); more investigation is warranted here. #########=##------############################======##%%%###----- .. ######## ----------------#######=##%%###------ .. ####### ==== ##########%%##------ .. I initially designed CIL with the thought that it could be used for ######## ====== ######---###------- .. multiple such "compile C to X" projects. These are primarily jokes, ######## ==------ ======== ######---------- .. but can occasionally be of legitimate use for low-level ####### ========== =-----= ######-------- .. domain-specific tasks where the existence of a reasonable and familiar ##### === ######------- .. high-level syntax pays for the effort of writing a simple backend. ### ========== == =------== ######-------- .. (When making such a decision I like to also weight the effort by the ### ========== ##-------- .. enjoyment of each task: i.e., the cost is like ## ##------- .. ## == ====== ##------- .. (1 - fun of writing backend) * time writing backend vs ### ========== ----======= == ##------- .. (pain of writing low-level code by hand) * ### =========== ===-- ##------- .. time writing low-level code by hand ############# === === === #--- .. ################ ## .. ... but I have been informed that not all computer work is done purely ############## .. for fun.) This "portable assembler" application of C remains relevant .. today, and CIL or LLVMNOP is a much simpler than GCC or LLVM. Figure 7. Printable X86 .. .. Anyway, I discovered that the design of such a thing is not so easy. .. While it is possible to "compile away" certain features by turning .. them into something "simpler," it's not straightforward what feature ** 32. Acknowledgements ** .. set to target. For example, for ABC, we compile away the | operator .. into &, ^, -, and +1. In another setting, | may very well be present The author would like to thank the fastidious SIBOVIK "Program" Committee .. instead of &. We normally think of the >> and << shift operators as for "Evaluating" my paper. .. being fundamental, but in ABC they are inaccessible. I find the .. expression forms like "a < b" much easier to think about then the .. combined test-and-branch version, but the latter is much better when ** 33. Bibliography ** .. targeting x86, and important for producing reasonable code in ABC. I .. do think it would be possible to develop a simple and general language .. for this niche where certain constructs could be compiled away in [KNPH'14] Tom Murphy VII. "New results in k/n Power-Hours." SIGBOVIK, .. favor of others, at the direction of the compiler author, but such a April 2014. .. thing is firmly future work. .. [MTMC'08] Tom Murphy VII. "Modal Types for Mobile Code." Ph.D. thesis, .. Carnegie Mellon University, January 2008. Technical report .. On the topic of taking away, one might ask: What is the minimal subset CMU-CS-08-126. .. of bytes we could imagine using? .. [LLVM'04] Chris Lattner and Vikram Avde. "LLVM: A Compilation Framework for .. There are some trivial subtractions: We never emit the BOUND Lifelong Program Analysis and Transformation." CGO, March 2004. .. instruction (0x62, lowercase b) and it does not seem useful; a few of .. the segment prefix instructions are also unused. The instructions like [CKIT'00] David Ladd, Satish Chandra, Michael Siff, Nevin Heintze, Dino .. "ASCII Adjust After Addition" are currently unused, but since they act Oliva, and Dave MacQueen. "Ckit: A front end for C in SML." March .. on AX in a predictable way, they could provide ways to improve the 2000. http://smlnj.org/doc/ckit/ .. routines to load immediate values. But we're talking about reducing .. the surface, not increasing it. And speaking of loading immediate [INTC'01] Intel Corporation. "IA-32 Intel Architecture Software Developer's .. values, we do certainly make use of the entire set of printable bytes Manual. Volume 2: Instruction Set Reference." 2001. .. in these routines (as arguments to XOR, SUB, PUSH, etc.), but on the .. other hand, we can also reach any value from a known starting point by [ABC'05] Steve Mansfield. "How to interpret abc music notation." 2005. .. INC and DEC, taking at most 0x7FFF instructions (half the size of the .. code segment, unfortunately). More essential is our ability to set a [MOV'13] Stephen Dolan. "mov is Turing-complete". 2013. .. register to a known value, which today requires two or more printable .. values whose bitwise AND is 0. Sadly, though we could go through some [MVF'16] Chris Domas. "M/o/Vfuscator2". August 2015. .. pains to remove bytes from the gamut here and there, no natural subset https://github.com/xoreaxeaxeax/movfuscator .. like "lowercase letters" or "alphanumeric" jumps out; we rely on the .. control flow in the late lowercase letters (Jcc) and the basic ops in .. the early punctuation (AND/XOR), not to mention that the EXE header Please see http://tom7.org/abc for supplemental material. .. barely works within the existing constraints with access to both .. "small" (0x2020) and "large" (0x7e7e) constants. .. .. Others have produced compilers for high-level languages with very .. reduced instruction sets. In an extreme case, Dolan shows [MOV'13] .. that the mov instruction on its own is Turing-complete (note however .. that this requires a "single absolute jump" to the top of the program, .. an issue similar to what we encounter in printable x86, only we do not .. cheat by inserting any out-of-gamut instructions). Another .. enterprising programmer, Domas, implemented a C compiler that produces .. only MOV instructions [MVF'16]. I didn't look at it while writing ABC .. (spoilers!) but he avoids using any JMP instruction the same way that .. I exit the program (generating illegal instructions but rewriting the .. interrupt handler). While awesome, the problem is somewhat different .. from what ABC solves; here we are fundamentally concerned with what .. bytes appear in the executable, which influences what opcodes are .. accessible (and their arguments and addressing modes), but is not the .. only constraint created. For example, in MOV-only compilation, the .. program's header does not need to consist only of MOV instructions, .. and so the compiler's output does not suffer the same severe code and .. data limitations that DOS EXEs do. (The executables it produces are .. extremely large and slow; they also seem to have non-MOV .. initialization code.) The MOV instruction is also very rich, and no .. versions of it are printable! .. .. Of course, everyone knows that even unary numbers (just like one .. symbol repeated a given number of times) is Turing complete, via Godel .. .+..............................................................................................................................................................++..............................................................................................................................................................+. .. ** Appendix ** .. .. Here is a histogram of every character that appears in .. this file. There are no non-printable bytes. .. .. .. char byte number of occurrences .. 0x20 179076 .. ! 0x21 1242 .. " 0x22 1771 .. # 0x23 3216 .. $ 0x24 557 This .. % 0x25 3922 .. & 0x26 113 column .. ' 0x27 467 .. ( 0x28 8770 is .. ) 0x29 955 .. * 0x2A 528 unintentionally .. + 0x2B 233 .. , 0x2C 2088 left .. - 0x2D 27690 .. . 0x2E 7339 blank. .. / 0x2F 252 .. 0 0x30 1045 .. 1 0x31 1618 .. 2 0x32 697 .. 3 0x33 1346 .. 4 0x34 2594 . .. 5 0x35 373 .. 6 0x36 321 .. 7 0x37 185 .. 8 0x38 296 .. 9 0x39 102 . .. : 0x3A 1423 .. ; 0x3B 306 .. < 0x3C 482 .. = 0x3D 1048 .. > 0x3E 49 .. ? 0x3F 96 . .. @ 0x40 11867 .. A 0x41 559 .. B 0x42 366 .. C 0x43 664 .. D 0x44 1516 .. E 0x45 2762 .. F 0x46 860 .. G 0x47 200 .. H 0x48 291 . .. I 0x49 422 .. J 0x4A 118 .. K 0x4B 192 .. L 0x4C 261 .. M 0x4D 490 .. N 0x4E 773 .. O 0x4F 248 OR IS IT ?!?! .. P 0x50 2784 .. Q 0x51 8236 .. R 0x52 147 .. S 0x53 375 .. T 0x54 372 .. U 0x55 154 .. V 0x56 81 .. W 0x57 127 .. X 0x58 2821 .. Y 0x59 62 .. Z 0x5A 143 .. [ 0x5B 105 .. \ 0x5C 132 .. ] 0x5D 459 .. ^ 0x5E 946 .. _ 0x5F 23111 .. ` 0x60 38 .. a 0x61 5342 .. b 0x62 1268 .. c 0x63 2527 .. d 0x64 2370 .. e 0x65 8806 .. f 0x66 1381 .. g 0x67 4943 .. h 0x68 3456 .. i 0x69 5406 .. j 0x6A 1638 .. k 0x6B 513 .. l 0x6C 3072 .. m 0x6D 1988 .. n 0x6E 5008 .. o 0x6F 5159 .. p 0x70 1763 .. q 0x71 209 .. r 0x72 4376 .. s 0x73 4917 .. t 0x74 7297 .. u 0x75 3625 .. v 0x76 677 .. w 0x77 1219 .. x 0x78 837 .. y 0x79 1035 .. z 0x7A 361 .. { 0x7B 73 .. | 0x7C 138 .. } 0x7D 1188 .. ~ 0x7E 17126 .. total 409600 .. .. .. The following characters were inserted to make the .. above converge: 000004669 .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .+..............................................................................................................................................................+