I was recently reminded of an mid-1990s comp.lang.asm thread about programming in x86 assembler... using just the ASCII characters-- that is, the opcodes and oprands being limited to 0x20-0x7E --and the newsgroup went as far as producing a xmodem bootloader (..and quite possibly inspired a lot of exploit shellcode over the following decade..). The idea is certainly older than the 1990s, as ZX81 programers had their machine-code-in-REM statements trick. I checked and I couldn't really find anything for the 6502, which left me sleepless, so I started researching it myself.
With the vanilla 6502 CPU, $20-$7E doesn't give you much-- however most importantly the JSR, JMP, and RTS instructions is in there, and SEC BMI PHA PLA AND BIT ROL EOR LSR ADC ROR are what you have to work with. (SEI CLI PLP BVC BVS RTI as well, but they're next to useless here.)
The only memory-modifying (outside of the stack) instructions in the set are the bit-rolling ops ROL, ROR, and LSR-- so if you want to write to memory... guess what you've got to do!
Code:
.macro STA_ZP xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
rol a
rol xx
.endmacro
All the register-immediate instructions are up around $Ax, so a LDA_XXXX macro is needed, or a bit more efficiently:
Code:
.macro LDA_IMZ
and #64
and #32
.endmacro
.macro LDA_IM xx ;32<=x<=126
LDA_IMZ
eor #xx
.endmacro
.macro LDA_IMHE xx ;even values 64<=x<=254
LDA_IMZ
eor #(xx >> 1)
sec
adc #((xx >> 1)-1)
.endmacro
Executable code-- at least anywhere you need to read, write, branch or jump to, has to be in areas of memory with the $20-$7E limit as well ($2020-$207E, $2120-$217E, ... $7E20-$7E7E). Code can reside beyond those areas, however it won't be possible to refer to it (LD/ST/JSR/JMP).
The only practical branch instruction BMI can only skip forward $20-$7E bytes.
What does an actual working 'printable 6502 machine code program' look like you ask? :) .. Here's a sample!
Code:
A`A`A`A`)@) IU8iT*. *. *. *. *. *. *. *. )@) IE8iD*." *." *." *." *." *." *." *." )@) It8is*.$ *.$ *.$ *.$ *.$ *.$ *.$ *.$ )@) I(JJ*.& *.& *.& *.& *.& *.& *.& *.& )@) L !
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
> "*> "*> "*> "*> "*> "*> "*> "* & & )< & & *&"*&"*&"*&"*&"*&"*&"*&" $ > "*> "*> "*> "*> "*> "*> "*> "* & & )<JJE"H " j) h*> "*> "*> "*> "*> "*> "*> "*> " " 8*i 0TL !
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
jm2n22o00820m2oonn2122m0o360495420574o524k53210m00
(Edit: oops, the case on the letters is swapped, and the code isn't relocatable, but you get the idea..)
...this is Shellcode for the Commodore 64! :D ... The assembler source:
http://kildall.apana.org.au/~cjb/C64/decode-ascii.asmProgress photos from this little experiment are on my Twitter account :
https://twitter.com/Chris_J_Baird/status/867611129073160192 ...
https://twitter.com/Chris_J_Baird/status/867170413444333568