I have now stepped through KQ1's virtual machine enough to gain a basic understanding of how it works, and what the logic file format is like.
There are only two directories: rooms, and views. The directory is in absolute sector number 10, which in most disk images is at file offset 0x1400. The room directory is in the first 512 bytes, the view directory in the second 512 bytes. Each entry is of double-word size, containing the absolute start sector number, the number of sectors, and the starting offset into the start sector number. A room begins with the logic resource, directly followed by the picture resource. Resources have no headers, unlike AGI, where they start with 0x12 0x34 volume sizeLo sizeHi.
The first four words of the logic resource contain the four lengths, in bytes, of the four blocks of a logic resource. The game finds the picture resource simply by adding those four values together, thus seeking past the logic resource. The four blocks are:
- Action subroutines
- Test commands evaluated continously
- Test commands and parser word tokens evaluated when a line is entered
- Text strings
The test command blocks contain only a sequence of test commands (and, in the case of 3., parser word tokens), each followed by an offset into the Action subroutine block that is executed if the test evaluates positively. There seem to be only three test commands: equaln, not equaln, and posn. From what I have seen, there are only vars, no flags.
The string block has no pointers, only zero-terminated strings. Their offsets into the string block are hard-coded as parameters of the action commands themselves, instead of using message numbers.
There is no separate directory for sound resources. Room numbers 90 and up only contain sound resources, four in each room. The four words that in a normal room resource would contain the length of the four logic blocks here contain the length of each of the four sound resources that make up the "room" resource.
I suppose it's a matter of definition whether to call this thing AGI or not. The action and test commands do not match their respective AGI opcode numbers at all. For example, the functional equivalent to new.room is command 0x04, and new.room.f is command 0x37. Given all these differences, I'm more inclined to keep calling KQ1's language GAL, even as it is tempting to see the similarities. Maybe if the
logic format of the AGI version 1 interpreter were fully known, one would see more of a continuity between GAL and AGI version 2.
Knowing the structure of the logic resource as well as the length of all opcodes, but lacking knowledge of the meaning of the variables and some of the opcodes, where the disassembly is too obtuse, the next step for me will be to write a small decompiler of the logic resources. That will allow me to compare the output to AGIv2 KQ1.
What that looks like to me, is a routine coded in assembly being wrapped to make it callable from a HLL.
No, that's not what it is. Wrapping an assembly routine to be HLL-callable would look something like this:
CLoadRoom proto c, roomNumber: word
CLoadRoom proc near
push bp
mov bp, sp
mov ax, [bp+4]
call AsmLoadRoom
pop bp
ret
CLoadRoom endp
AsmLoadRoom proc near
; call with: AX=new room number
shl ax, 1
shl ax, 1
mov bx, ax
mov ax, [roomDirectory+bx]
(...)
ret
AsmLoadRoom endp
What we have in KQ1 instead is the calling procedure establishing a stack frame, and all the BP references of the callee being relative to the stack frame that the caller has set up. That's extremely unusual.
It's also somewhat unusual to reserve room for local variables using "SUB SP, xx"
before the "MOV BP, SP". It makes each argument's relative offset a function of the number of local variables. Normal compilers, including the one used to compile both AGI v1 and v2, subtract from SP
after "MOV BP, SP". That way, arguments can be identified by BP+x, while locals are BP-x. That is the standard behavior, so standard that Intel turned into its own machine language instruction (ENTER) on their 1982's 80286 processor.
From what I've seen there is no oddness here, only a mixture of C and ASM,
I suggest you look at my first example more closely. The caller function does not start with the PUSH BP; that occurs right in the middle of it.