As mentioned before there are a lot of tables, and even a couple of tables of tables. Some are lists of 'described text' (having a header indicating position), some are indexed dispatch tables, some are double-dispatch, and the others are presently unknown.
I had previously thought the table-of-tables form was a double-dispatch mechanism, but it's not. Rather it's a list
Often they are processed by RST 10, which delegates processing of the elements to RST 8. The elements are variably-sized blocks. They seem to have the structure:
uint8_t fxn;
uint16le_t param1;
uint16le_t param2;
... payload;
(I am coining the term 'uint16le_t' because I have found many places that are big-endian! So elsewhere I use 'uint16be_t' for that.)
Often the blocks are short, like this:
7817 62 byte_7817:db 62h 7818 95 7A dw unk_7A95 781A 11 00 dw 11h 781C 00 db 0
but others can be quite long. I originally thought param1 is a pointer, and params is a length, because the RST 8 code that processed them immediately loads param1 into HL and param2 into DE. Indeed sometimes those are used as pointers and lengths, but in other cases they are not. But for starters I look at the ones at off_7803, which is a list of these things which have just one element in them, and seem to follow the (ptr,length) assumption. The trailing zero is interesting. All the ptrs and lengths worked out sanely when updating the the disassembly.
These blocks are ultimately processed by RST 10. This is a block sequencer, feeding individual blocks to RST 8 (which expects the block pointer in IX). A null function code terminates processing, so that explains the trailing 'db 0' above. So there is no explicit payload length of a block; it depends on function code.
RST 8 dispatches servicing through dispatch_4002, which has 128(!) entries. I went through and labelled each of them like 'fxn00'. Many of them are apparently unimplemented as their slot directs to 'fxn00', which was found to be used as the block sequence terminator. It does have an implementation, though, which is to back up IX by 4. This is interesting because fxn00 will not be dispatched by RST 10, but it would for the ostensibly unimplemented function codes. Turns out that RST is optimistically loading param1 and param2 and incrementing IX just past. So the IX-=4 is to undo that optimistic loading and continue at the byte following the unimplemented function code. So 'ignore unimplemented function code'.
In the end, there are 76 implemented and 52 unimplemented functions.
After labelling, I looked at fxn62h, since that was the the code used in the blocks above. The implementation boggled my mind a bit. It did some sort of queueing into c200, which is structured as 32 8-byte entries. A gave up with that and scrolled through the nearby disassembly casually and found fxn63 just below it, that also did something similar with c200, and then invoked my buddy RST 10 -- the 'block list dispatcher'. So if fxn62 puts it in, and fxn63 takes it out and processes it, this seems evocative of a 'load' and 'run' functionality. Anyway, there was still too many unknowns so I started to look for smaller fish to fry.
Scrolling through I found some shorter ones that I could comprehend, and coincidentally these tended to be the smaller numbered function codes. The first one I found was:
43FD ; XXX 3f: dispatch to param1 (no param2) 43FD fxn3f_43FD: 43FD DD 2B dec ix ; (no param2) 43FF DD 2B dec ix 4401 E9 jp (hl) ; thunk over
So this would run arbitrary external code.
Another is a conditional 'goto' of sorts, where the block processing is directed to another place (hopefully within the list!) if --*((uint8_t*)param2) != 0:
43D3 ; XXX 3c: goto block @ param1
43D3 fxn3c_43D3:
43D3 EB ex de, hl
43D4 35 dec (hl)
43D5 28 03 jr z, leave_43DA ; leave if --*((uint8_t*)param2) == 0
43D7 D5 push de
43D8 DD E1 pop ix
43DA leave_43DA:
43DA C9 ret
So this seems to be implementing a form of 'next' using an implicit down counter. Similarly, there is an 'if':
43B2 ; XXX 3a: if ( *((uint8_t*)param2) ), goto param1
43B2 fxn3a_43B2:
43B2 1A ld a, (de)
43B3 B7 or a
43B4 28 03 jr z, leave_43B9
43B6 E5 push hl
43B7 DD E1 pop ix
43B9 leave_43B9:
43B9 C9 ret
So these 'blocks' seem to suggest being a 'byte code' of sorts for a virtual machine.
It's worth noting also that most values referenced within this machine are treated big-endian; e.g.:
434D ; XXX 2d(4): *(uint16be_t*)param1 <<= param2.l
434D fxn2d_434D:
434D C5 push bc ; save
434E 46 ld b, (hl) ; high byte first!
434F 23 inc hl
4350 4E ld c, (hl) ; then low byte!
...
However, param1 and param2 are host cpu native, i.e. little-endian. I add the notation 'param2.l' to make it clearer which byte in a neutral way.
Along the way I also found and, or, xor. But then I needed to call it a night, but lot's more to do in this area. Since the Cefucom seems to embed scripts for this thing, it may be useful for me to make a disassembler for it. This will be a little challenge since the 'compiled' scripts have absolute memory references, but I can probably manage. It will take some effort, but it might be worth it for more rapidly understanding what's going on.
It was a thing back then to use 'p-code' as a means of achieving code density. This is not p-code per se, because it's not (p)ortable at all. It has absolute address references and is mixed-endian. But it seems to be in that spirit.
A non-important observation is that all this code is located from 3f00-4dff. (there is a couple dead bytes at the beginning and many at the end). So it feels like a distinct 'component' of sorts. 3840 bytes for this 'virtual machine'.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.