Due to the way Dragon's Lair was written (and even Thayer's Quest!), I assumed Halcyon would have a similar style. But after a first pass at the Z80 disassembly, I could not find these routines anywhere. To make matters worse, Halcyon uses a different laserdisc player than the rest of the laserdisc games, the Pioneer LD-700. So while I had written an interpreter for this command set (in order to get Dexter to work with Halcyon), I still had no idea how the "RDI engineering team" would approach implementing it.
My guess was that they would write a function to take in a command byte (such as 0x4A) and then have a common set of routines that sent this function serially across the single wire. What I actually found astonished me.
Within the ROM, I found tables like this:
ROM:9165 unk_9165: db 4 ; DATA XREF: ROM:8A31↑o
ROM:9165 ; ROM:8F07↑o
ROM:9165 ; see 8A31
ROM:9166 db 2
ROM:9167 db 2
ROM:9168 db 1
ROM:9169 db 1
ROM:916A db 1
ROM:916B db 2
ROM:916C db 2
ROM:916D db 3
ROM:916E db 1
ROM:916F db 2
ROM:9170 db 4
ROM:9171 db 3
ROM:9172 db 2
ROM:9173 db 1
ROM:9174 db 1
ROM:9175 db 1
ROM:9176 db 0
I was pretty sure that this represented laserdisc commands, but just by eyeballing it, I couldn't see how. I eventually had to emulate the Halcyon in a WIP verion of Daphne v2.0 just enough to step through the ROM and observe the program flow. This required me to basically emulate the entire COP421L microcontroller too, as well as wire up most of the Z80 ports, so it was exceedingly NON-TRIVIAL to get this far.I found routines that I became increasingly confident were related to laserdisc player I/O. I found arrays full of pointers to more arrays like the above example. But what confused me was I found two distinct tables of arrays, which lead me to suspect that the ROM was designed to support two different kinds of laserdisc players.
Using Daphne's debugger, I stepped into the routine that sent a command to the LD-700. I've included a partially annotated version below:
ROM:8DF3 SendCmdToLD700: ; CODE XREF: ROM:8A34↑p
ROM:8DF3 ; ROM:8A37↑j ...
ROM:8DF3 di ; HL points to a null-terminated array containing small integers like 4, 3, 2, etc.
ROM:8DF3 ;
ROM:8DF3 ; This may send a command to the LDP because interrupts get disabled (and the LDP commands are time sensitive).
ROM:8DF4 ld a, (Port20Cache) ; Caches value stored at port 20. see 441
ROM:8DF7 and 0FEh ; clear LDP_EXT_CTRL' bit
ROM:8DF9 ld d, a ; D contains port20cache with bit0 clear
ROM:8DFA push hl ; store HL (which is pointing to the array of small integers)
ROM:8DFB res 0, d ; may be redundant since bit 0 seemed to be already clear, but whatever
ROM:8DFD ld bc, 332 ; ~8ms delay for "leader down" ?
ROM:8E00 call SendDToPort20hLoopOnBC
ROM:8E03 set 0, d ; set/raise LDP_EXT_CTRL'
ROM:8E05 ld bc, 158 ; ~4ms delay for "leader up"
ROM:8E08 bit 7, (hl) ; kill some time
ROM:8E0A bit 7, (hl)
ROM:8E0C bit 7, (hl)
ROM:8E0E bit 7, (hl)
ROM:8E10 bit 7, (hl)
ROM:8E12 call SendDToPort20hLoopOnBC
ROM:8E15 ld a, r
ROM:8E17
ROM:8E17 LdpArrayLoop: ; CODE XREF: ROM:8E35↓j
ROM:8E17 ld b, (hl) ; grab the next item in the null-terminated array
ROM:8E18 xor a ; A = 0
ROM:8E19 or b ; A = B, test to see if we've hit the end of the array (NULL)
ROM:8E1A inc hl ; arrayPtr++
ROM:8E1B jr z, OnLdpArrayExhausted ; come here when we've hit the end of the array
ROM:8E1D call ProcessSmallIntForLDP
ROM:8E20 set 0, d
ROM:8E22 ld bc, 38 ; convert the last sent bit from a '0' to a '1' by holding the line an extra 1ms longer
ROM:8E25 call SendDToPort20hLoopOnBC
ROM:8E28 ld a, r
ROM:8E2A ld a, r
ROM:8E2C ld a, r
ROM:8E2E ld a, r
ROM:8E30 ld a, r
ROM:8E32 ld a, r
ROM:8E34 or a
ROM:8E35 jr LdpArrayLoop ; grab the next item in the null-terminated array
ROM:8E37 ; ---------------------------------------------------------------------------
ROM:8E37
ROM:8E37 OnLdpArrayExhausted: ; CODE XREF: ROM:8E1B↑j
ROM:8E37 set 0, d ; come here when we've hit the end of the array
ROM:8E39 ld bc, 759 ; 20ms delay after a command is finished, holding line high?
ROM:8E3C call SendDToPort20hLoopOnBC
ROM:8E3F bit 7, (hl)
ROM:8E41 ld a, r
ROM:8E43 ld a, r
ROM:8E45 ld a, r
ROM:8E47 ld a, r
ROM:8E49 ld a, r
ROM:8E4B ld a, r
ROM:8E4D or a
ROM:8E4E pop hl
ROM:8E4F ei ; re-enable interrupts, the timing sensitive stuff is finished
ROM:8E50 ld a, (Port20Cache) ; Caches value stored at port 20. see 441
ROM:8E53 set 0, a ; raise LD_EXT_CTRL'
ROM:8E53 ; (may be redundant, as this may have already been raised)
ROM:8E55 ld (Port20Cache), a ; Caches value stored at port 20. see 441
ROM:8E58 out (20h), a
ROM:8E5A jp ConditionalLdpReturn
ROM:8E5D
ROM:8E5D ProcessSmallIntForLDP: ; CODE XREF: ROM:8E1D↑p
ROM:8E5D ; ROM:8E7F↓j
ROM:8E5D push bc ; when this function is called, A and B may both contain the mystery small integers (4, 3, 2, etc)
ROM:8E5E ld bc, 20 ; ~0.5ms pulse width.
ROM:8E61 res 0, d
ROM:8E63 call SendDToPort20hLoopOnBC
ROM:8E66 set 0, d
ROM:8E68 ld bc, 18
ROM:8E6B bit 7, (hl) ; kill some cycles
ROM:8E6D bit 7, (hl)
ROM:8E6F bit 7, (hl)
ROM:8E71 bit 7, (hl)
ROM:8E73 bit 7, (hl)
ROM:8E75 call SendDToPort20hLoopOnBC
ROM:8E78 pop bc
ROM:8E79 bit 7, (hl) ; kill cycles
ROM:8E7B ld a, 0
ROM:8E7D ld a, 0
ROM:8E7F djnz ProcessSmallIntForLDP
ROM:8E81 bit 7, (hl)
ROM:8E83 bit 7, (hl)
ROM:8E85 bit 7, (hl)
ROM:8E87 ld a, r
ROM:8E89 ret
ROM:8E8A
ROM:8E8A SendDToPort20hLoopOnBC: ; CODE XREF: ROM:8E00↑p
ROM:8E8A ; ROM:8E12↑p ...
ROM:8E8A ld a, d ; this is almost assuredly used as part of the LDP communication
ROM:8E8B out (20h), a
ROM:8E8D rl a ; these opcodes may just be designed to cause delay
ROM:8E8F rl a
ROM:8E91 rl a
ROM:8E93 ld a, d
ROM:8E94 ld a, d
ROM:8E95 and 0FEh ; isolate LD_EXT_ACK
ROM:8E97 in a, (40h) ; read LD_EXT_ACK (but apparentely ignore the result?)
ROM:8E99 ld a, r
ROM:8E9B dec bc
ROM:8E9C ld a, c
ROM:8E9D or b
ROM:8E9E jr nz, SendDToPort20hLoopOnBC ; loop until BC is 0
ROM:8EA0 ret
After stepping through the loop, it became clear to me what those small integers (4,3,2,1) referred to and I finally cracked the code.Here is my comment for the 4Ah instruction for the LD-700.
ROM:9177 LD700Cmd4AEnableAudio_Idx1A:db 4 ; DATA XREF: ROM:8F09↑o
ROM:9177 ; Each of these integers represents a group of bits, where all of the bits are 0 except the last bit.
ROM:9177 ;
ROM:9177 ; So the 0xA8 command that the LD-700 expects to receive first will be encoded here as:
ROM:9177 ; 4
ROM:9177 ; 2
ROM:9177 ; 2
ROM:9177 ;
ROM:9177 ; The 4 means three 0-bits followed by one 1-bit (8 if bits are reversed)
ROM:9177 ; The 2 means one 0-bit followed by one 1-bit (so a pair of 2's is 0x0A if bits are reversed)
ROM:9178 db 2 ; 0x0A (01 01 LSB first)
ROM:9179 db 2
ROM:917A db 1 ; The required 0x57 the LD-700 expects is encoded as
ROM:917A ; 1
ROM:917A ; 1
ROM:917A ; 1
ROM:917A ; 2
ROM:917A ; 2
ROM:917A ; N
ROM:917A ;
ROM:917A ; which in binary ends up being 11101010 (0x57 with bits reversed)
ROM:917A ;
ROM:917A ; The final value is a 0-bit so it has to be merged with the upcoming byte (3).
ROM:917B db 1
ROM:917C db 1
ROM:917D db 2
ROM:917E db 2
ROM:917F db 3 ; final 0-bit of the 0x57, followed by 01.
ROM:917F ;
ROM:917F ; The final bytes end up being 0x4A 0xB5 (0x4A ^ 0xFF)
ROM:9180 db 2 ; 01
ROM:9181 db 3 ; 001
ROM:9182 db 2 ; 01
ROM:9183 db 2 ; 01
ROM:9184 db 2 ; 01
ROM:9185 db 1 ; 1
ROM:9186 db 2 ; 01
ROM:9187 db 1 ; 1 (trailing)
ROM:9188 db 0
I applied this same logic to the unknown alternate laserdisc player set of commands and ended up with a full **PR-8210** command set!So apparently the Halcyon was designed (at least at first) to work with the PR-8210 or some IR compatible player. Since the Halcyon detects when the tray is ejected or not, I doubt they actually used the PR-8210 in production. But the code is left in the ROM for posterity. Really cool!
I was really shocked that they encoded the commands in the ROM this way instead of having a nice function encode dynamically. But in retrospect, it kind of makes sense. Since some of the bits of one byte need to combined with bits from the next byte, whipping up this algorithm in Z80 assembly language may have been pretty tricky. So the developer(s), likely being under a time crunch and realizing they had some extra space in the ROM, probably just said "Let's just store the commands already encoded in the ROM to simplify our task!"
The actual story will likely remain a mystery forever... *ominous music*
No comments:
Post a Comment