A couple of days ago I wrote a blog post about being able to read QL executable file headers so that you can restore them from original QL applications on QL disks, onto a modern OS which lacks them when running a QL emulator.
https://oneweekwonder.blogspot.com/
It's actually fairly involved here, involving one SuperBASIC program to load a machine code hex program (and then save it on disk); then write another SuperBASIC program to load that code, which adds a couple of functions to SuperBasic. And to make it even more complex I ended up wrapping them with SuperBASIC procedures to make opening the channel so you can use the SuperBASIC extensions and then closing it at the end.
It turns out though you can make it much shorter, a single, simple SuperBASIC program:
10 mcode=RESPR(8):buffer=RESPR(64):POKE_L mcode,0+"536956483":POKE_L mcode+4,0+"1879068277"
100 DEFine PROCedure GetHead(fName$)
110 OPEN #3,fName$:CALL mcode,71,64,500,0,0,0,0,ChanId(3),buffer
120 PRINT "Len:";PEEK_L(buffer);" Dataspace:";PEEK_L(buffer+6)
130 CLOSE #3
140 END DEFine
150 DEFine FuNction ChanId(chan):LOCal a6:a6=PEEK_L(163856)+104
160 RETURN PEEK_L(PEEK_L(a6+48)+a6+chan*40):END DEFine
It does the same thing. You should run the program, and then you can type things like: GetHead "mdv1_quill" to return the size of the program and the data space.
It nicely illustrates some of the impacts of having a fancy version of Basic (for the day) and the relative complexities of Sinclair's QDOS operating system.
In 1984, when the QL appeared, not many computers had 128kB of RAM. The Macintosh had just appeared, with 128kB. Most PCs were still in an era where 128kB was fairly normal; the PC/XT came with a standard 128kB as did the Sirius One. Probably most original PCs were still living with ≤128kB. The Atari ST, Amiga, PC/AT, BBC Master 128, Commodore 128, Amstrad CPC128 were all in the future.
So, to see a new computer with 128kB was quite a luxury. And since it had a structured BASIC it was very tempting to try and write elegant, i.e. wordy, code. Hence the Hex loader, which would have been a tiny thing on a ZX81 or ZX Spectrum was a couple of dozen lines long, containing multiple procedures, just to prove the programmer understood the concepts.
Wordiness can obscure as much as it reveals though. For example, I think this version is actually simpler to understand, so let's go through it.
Firstly, we allocate just 8 bytes for machine code and poke_l it in directly. It's just 4 instructions:
2001 MOVE.L d1,d0; because d0=function code and we can't pass d0 in CALL.
4E43 TRAP #3 ;all the other parameters are supplied in the CALL.
7000 MOVEQ #0,d0 ;return with no errors regardless.
4E75 RTS ;back to BASIC.
I had to use 0+"integer" in the POKE_L statements, because SuperBASIC will convert large integers into scientific notation with just 6 significant figures. 0+"integer" will perform a VAL("integer") thanks to SuperBASIC's type coercion.
As before, we also set up a 64 byte buffer.
The GetHead procedure takes fName$ as before; we open up the channel (3) and pass that to the CALL statement along with the function code (71), the size of the buffer (64), the timeout (10s); then channel Id (ChanId(3)) and finally the buffer address. All these parameters are copied directly to 68000 registers.
The complexity lies in the function ChanId(3). QDOS doesn't use SuperBASIC channel numbers as its Channel IDs, instead they're a 32-bit number that's not trivial to derive. So, you have to indirect it via the system variables into the SuperBasic variables and then from there calculate the offset into the channel table!
My method isn't fool-proof. A proper routine should calculate all of the relative to A6, because it could function as part of a multitasking job, where A6 could end up getting moved around. In my simplistic implementation, SuperBASIC is the only job running, so A6 won't change and can be calculated statically.
But the upshot is that you can simplify GetHead. You could also use the same machine code routine to implement SetHead, since it's really just a TRAP #3.