That's really cool. Though I'd be looking to use GAS and LD if I were doing this, so you can have labels and macros, etc. - they're an important part of assembly, after all. You might need a simple linker script, I'm not sure it understands COM directly.
If you've not seen it yet, the osdev.org wiki is really good for anything low-level like this.
Honestly I have MASM somewhere, but doing such a ridiculous thing entirely in debug - which literally came with DOS - just had a weird appeal to it. Anyone with an ancient DOS computer could just pipe that code in and it'd assemble it. It even has the debug commands that after it's assembled will write it to a .com that DOS can natively load and run.
An .exe had a certain amount of overhead that I never delved deeply enough to understand, but a .com was literally just raw bytecode. It's loaded in to address 0100 and it just starts execution from there.
assemblers can and will use longer/shorter versions of instructions with the same mnemonic
Yes, but this wasn't really a problem: debug was able to assemble them correctly, most of the time anyway. It was certainly able to tell the difference between
add ax,dx
(a 2-byte opcode) and
add ax,[077E]
(4 bytes). My real problem was that conditional jump opcodes only have a short jump format; I had to pipe the code into debug, pipe the output into a file, paste that output into my "linker" and then see whether there were any errors due to trying to conditionally jump to an address farther than +127/-128 bytes away. (In its summary, it would identify any errors in the pasted output.)
Updating the addresses could result in a conditional jump not being possible. Sometimes this was just because the address needed to be updated a second time, other times I had to replace the conditional jump with a conditional jump-if-not to jump just past an unconditional jump to the intended destination (which could be assembled as a near jump instead of a short jump). Basically I always had to assemble the code at least twice, unless no addresses were changed & no errors occurred. Also, if I used a comment like
;+2
, my linker would insert the address of the 2nd
line of code following (not +2 bytes), so I didn't have to manually calculate how many bytes each line of code took. So I could easily just stick a short jump in without adding a label just to identify that I'm jumping past the next instruction.
Technically the code was also self-modifying, although only in the win/loss condition, which made it manageable. There is only one endgame routine; depending on whether you win or lose it'll overwrite the sprite address for unflagged but still covered mines (for "win", it should flag all of them, and for "lose" it's a bomb sprite... there is also an "uncovered mine" sprite; that didn't have to be patched, as it can only exist if you lost) and of course it also changes the address of the text that's printed out to say you won or lost.
This is extremely sketchy. What are you trying to achieve with this?
This was a rare exception to my "most of the time" caveat above. I wanted to use the commented
shr
instructions, and debug can't assemble them for some reason. It only seems to support single bit shifts.
I could either do a single bit shift n times, or I could use the x86 instruction reference and do it myself. I put the intended assembly code in a comment and then used
db
to put the assembled bytecode directly under it.
Of course,
/5
had to be found in this table... isn't that so much fun. Not exactly the most intuitive thing I've ever had to do...
Anyway, here's a full playthrough with a few mistakes (a few times I clicked the wrong square, the low res made it a bit tricky to hit the right one) but they luckily didn't kill me, but then in the end I had to start guessing and I uncovered a mine. So you get to see those sprites.
edit: and here's one I won, just to prove that I'm not entirely terrible at the game... although I can't say I didn't make any mistakes... bonus points if you spot it before I did...