Star Rider has almost worked perfectly with Dexter for quite some time and I had the opportunity to do more Star Rider research recently and here's some of my interesting findings.
First of all, the reason that Dexter wasn't working was because during the self-test, it would routinely fail with a search to frame 6 (as I recall) but would usually be able to successfully seek to other test frames on the disc. Why was it always failing to seek to frame 6?
Well, after much studying of the ROM disassembly, I think I have a pretty good grasp of how it works.
First of all, to initiate a seek, the Star Rider program calls a function at $7E75:
ROM3:7E75 ; =============== S U B R O U T I N E =======================================
ROM3:7E75 ; frame number stored in D
ROM3:7E75 ; on return carry may be set if advance button was pressed
ROM3:7E75 InitiateDiscSearch: ; CODE XREF: DiscTestSearchTo+2F P
ROM3:7E75 ; ROM3:7DE7 P ...
ROM3:7E75 pshs x,y,u
ROM3:7E77 pshs a,b
ROM3:7E7B std PifSendError ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E7B ; (see 19e2). Otherwise it will be 0.
ROM3:7E7E lda #$17
ROM3:7E80 sta PifResponseRetryCount
ROM3:7E83 puls b,a
ROM3:7E85 jsr SetupPifBuffersForSearching ; It appears that this is a "search to frame" function, where the frame is stored in D
ROM3:7E85 ; (A1BC needs to get a non-zero value stored to it or else it's an error)
ROM3:7E85 ; A1A4: 0x5 0x5 0x13 FrameHi FrameLo 0x5
ROM3:7E85 ; A1B4: FrameHi FrameLo
ROM3:7E85 ; A1BC: 0x13 0x0A 0x64
ROM3:7E85 ; A1A7 and A1B4 = D
ROM3:7E85 ; A1A4 = 0x505
ROM3:7E85 ; A1A6,A1BC = 0x13
ROM3:7E85 ; A1BD = 0xA64
ROM3:7E85 ; A1A9 = 5
ROM3:7E88 WaitSearchFinish: ; CODE XREF: InitiateDiscSearch+21 j
ROM3:7E89 ldd PifSendError ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E89 ; (see 19e2). Otherwise it will be 0.
ROM3:7E8C bne Done
ROM3:7E8E jsr IfAdvancePressedSetCarry ; set carry if advance is pressed
ROM3:7E8E ; clear carry if advance not pressed
ROM3:7E91 bcs Done
ROM3:7E93 jsr TestExpectedFrameNumStatus ; Does a TST on memory location A1BC
ROM3:7E93 ; Z will be set if expected frame number matches actual frame number.
ROM3:7E96 bne WaitSearchFinish
ROM3:7E98 ldd PifSendError ; Will contain an error code (such as 0x507) if trying to send commands to the PIF board or read laserdisc VBI data failed.
ROM3:7E98 ; (see 19e2). Otherwise it will be 0.
ROM3:7E9B Done: ; CODE XREF: InitiateDiscSearch+17 j
ROM3:7E9B ; InitiateDiscSearch+1C j
ROM3:7E9B puls pc,u,y,x
ROM3:7E9B ; End of function InitiateDiscSearch
The summary is that this method calls another function at $7E85 to store data in some buffers that will be later sent to the PIF board and then it waits for either the search to succeed ($7E98) or for the search to fail with an error ($7E8C).
Here is the function at $7E85:
ROM1:1C5B ; =============== S U B R O U T I N E =======================================
ROM1:1C5B ; It appears that this is a "search to frame" function, where the frame is stored in D
ROM1:1C5B ; (A1BC needs to get a non-zero value stored to it or else it's an error)
ROM1:1C5B ; A1A4: 0x5 0x5 0x13 FrameHi FrameLo 0x5
ROM1:1C5B ; A1B4: FrameHi FrameLo
ROM1:1C5B ; A1BC: 0x13 0x0A 0x64
ROM1:1C5B ; A1A7 and A1B4 = D
ROM1:1C5B ; A1A4 = 0x505
ROM1:1C5B ; A1A6,A1BC = 0x13
ROM1:1C5B ; A1BD = 0xA64
ROM1:1C5B ; A1A9 = 5
ROM1:1C5B SetupPifBuffersForSearching: ; CODE XREF: sub_0_35A0+C P
ROM1:1C5B ; sub_0_4471+98 P ...
ROM1:1C5B pshs cc
ROM1:1C5D orcc #$F0 ; '=' ; suppress interrupts
ROM1:1C5F std PifArray5Bytes_3
ROM1:1C62 std ExpectedHexPicNum ; The picture number we expect to be on (used to detect successful searches, etc).
ROM1:1C62 ; (see 1b9c)
ROM1:1C65 ldd #$505 ; 5 is a NOP command, so this starts with two NOP commands
ROM1:1C68 std PifArray5Bytes ; this array will be sent to pif board if the first byt is not zero
ROM1:1C68 ; it is 5 bytes in size
ROM1:1C6B lda #$13 ; 13 is the FIRQ cmd to initiate disc search
ROM1:1C6D sta PifArray5Bytes_2
ROM1:1C70 sta ExpectedFrameNumStatus ; May indicate current/pending pif command. (see 1a5f)
ROM1:1C70 ; gets 0x13 stored when a new search is initiated (FIRQ search command)
ROM1:1C73 ldd #$A64 ; 0xA is a slow decrementing counter that determines when we start calling the subroutine at 1b68,
ROM1:1C73 ; while 0x64 is an even slower decrementing counter that, if it reaches 0, triggers a 507 error (see 1b7f)
ROM1:1C76 stb SlowerPifCounter
ROM1:1C79 sta SlowPifCounter ; may throttle how often we check for seek complete among other things
ROM1:1C7C loc_0_1C7C: ; DATA XREF: sub_0_2DDA+19A r
ROM1:1C7C jsr Store5ToA1A9 ; A1A9 = 5
ROM1:1C7F puls pc,cc
ROM1:1C7F ; End of function SetupPifBuffersForSearching
This function was quite difficult to decode. It basically stores some commands to be sent to the PIF board at a later time in some buffers. Of particular note is a 5-byte array which will look like this: 0x05 0x05 0x13 FrameHi FrameLo. By disassembling the PIF board ROM, I was able to discover that command 0x5 is a single byte NO-OP while command 0x13 is a 'search to frame' command which is followed by the desired frame number in binary/hex format.
So when does this command actually get executed?
It happens during the IRQ service routine, which gets triggered on vblank.
We'll skip some details and start at $1A5F which gets called every top field and checks to see if there's anything to send to the PIF board. In our case, after the search command has been received, we will end up here:
ROM1:1A97 A1BCNotZeroAmongOtherTHings: ; CODE XREF: SendPifCmdsAndMore+5 j
ROM1:1A97 ; SendPifCmdsAndMore+A j ...
ROM1:1A97 orcc #$F0 ; '=' ; disable interrupts
ROM1:1A99 lda PIA_VGG_U7_CTRL_B ; PIF PIA
ROM1:1A9C lbpl DecreaseCounterAndCheck ; branch if pif board is not ready / waiting for seek to finish (?)
ROM1:1AA0 ldx NextPifBufStruct ; seems to contain a pointer to the next pif buffer structure after pif board becomes ready again (see 1AA0)
ROM1:1AA3 beq DecrementPriorityCounter ; branch if there is no active policy
ROM1:1AA5 ldb PifTimeoutBytesLeftToSend ; do we need to finish sending pif bytes we tried to send previously?
ROM1:1AA8 beq NoPifBytesLeftToSend ; if we have no bytes left to send from a previous attempt, branch
ROM1:1AAA ldu #PifTimeoutSendBuf ; If sending to pif timed out, this buffer
ROM1:1AAA ; will hold what we still need to send after
ROM1:1AAA ; the pif board becomes ready again.
ROM1:1AAA ; The array size is 4 bytes because the max
ROM1:1AAA ; size of a pif send buffer is 5, so at
ROM1:1AAA ; most 4 bytes will not have been sent if
ROM1:1AAA ; the pif board becomes busy.
ROM1:1AAD jsr SendCommandsToPif ; B holds how many bytes to send and will be modified
ROM1:1AAD ; U points to array of bytes to send and will be modified
ROM1:1AAD ; carry is set if we don't get the expected response
ROM1:1AB0 bcs OnPifTimeout ; if we timed out, it can mean that a seek has initiated (maybe can mean other things)
ROM1:1AB0 ; and so we should wait for the pif board to become ready
ROM1:1AB0 ; before sending anything else (within reason timeout period).
$1A99 checks to see if the PIF board is 'ready' or not. If it returns a result with the high-bit clear, it means the PIF board is not ready, so the program branches to the end of this function and just decrements a timer. If this timer expires, the PIF board is assumed to have become unresponsive and a fatal error is thrown. It is pretty important to understand that as long as the PIF board is 'busy', this method won't try to check to see whether the laserdisc is on the proper frame.
So once the PIF board has become non-busy, the program will then try to match up the frame number.
ROM1:1B68 ; =============== S U B R O U T I N E =======================================
ROM1:1B68 ; If enough time has passed, check to see if the expected picture number equals the actual picture number.
ROM1:1B68 ; If there is a discrepancy, send PIF jump commands to compensate.
ROM1:1B68 ; If enough time hasn't passed, this will update the timer/counter.
ROM1:1B68 ; This sets Z flag on success (D = 0) and return an error code in D on error.
ROM1:1B68 ThinkActualFrameNumIsExpected: ; CODE XREF: SendPifCmdsAndMore+5E P
ROM1:1B68 ; SendPifCmdsAndMore+98 P
ROM1:1B68 lda SlowPifCounter ; may throttle how often we check for seek complete among other things
ROM1:1B6B beq CompareActualPicNumWithExpected
ROM1:1B6D dec SlowPifCounter ; may throttle how often we check for seek complete among other things
ROM1:1B70 beq ReadActualPicNumIfAvailable
ROM1:1B72 jsr Store5ToA1A9 ; make sure that this gets called again
ROM1:1B75 clra ; sets Z flag to indicate success
ROM1:1B77 ; ---------------------------------------------------------------------------
ROM1:1B77 ReadActualPicNumIfAvailable: ; CODE XREF: ThinkActualFrameNumIsExpected+8 j
ROM1:1B77 lda #4
ROM1:1B79 sta SlowPifCounter ; may throttle how often we check for seek complete among other things
ROM1:1B7C dec SlowerPifCounter
ROM1:1B7F beq Error507 ; if this counter expires, it's an error
ROM1:1B81 lda FieldsLeftBeforeStability ; If this is 0, it means it's safe to read the VBI picture number. (See 1B81)
ROM1:1B81 ; Gets decremented by IRQ if it is not 0.
ROM1:1B81 ; It indicates how many fields the IRQ needs to see before it considers the software/hardware field values to be stable. (see e488)
ROM1:1B81 ; It gets set to non-zero any time the hardware field value does not match the software field value.
ROM1:1B84 bne Skip4TracksForward ; skip 4 tracks forward
ROM1:1B84 ; (to recover from bad VBI read?)
ROM1:1B86 jsr ConvertBCDPicNumToHexPicNum ; Converts the last read BCD picture number (from laserdisc VBI) to a hex picture number.
ROM1:1B86 ; Carry will be clear on success or set on failure.
ROM1:1B86 ; 'D' and $A133 will contain the converted picture number, or 50000 (decimal) on failure.
ROM1:1B89 bcc CompareActualPicNumWithExpected
ROM1:1B8B Skip4TracksForward: ; CODE XREF: ThinkActualFrameNumIsExpected+1C j
ROM1:1B8B lda #$10 ; skip 4 tracks forward
ROM1:1B8B ; (to recover from bad VBI read?)
ROM1:1B8D sta PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1B8D ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1B92 ; ---------------------------------------------------------------------------
ROM1:1B92 Error507: ; CODE XREF: ThinkActualFrameNumIsExpected+17 j
ROM1:1B92 ldd #$507 ; this may be an error code
ROM1:1B96 ; ---------------------------------------------------------------------------
ROM1:1B96 CompareActualPicNumWithExpected: ; CODE XREF: ThinkActualFrameNumIsExpected+3 j
ROM1:1B96 ; ThinkActualFrameNumIsExpected+21 j
ROM1:1B96 clr SlowPifCounter ; may throttle how often we check for seek complete among other things
ROM1:1B99 ldd CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1B99 ; (see 6f3)
ROM1:1B99 ; Gets 50000 (decimal) stored if VBI frame number appears to be invalid.
ROM1:1B99 ; (see 6E4)
ROM1:1B9C subd ExpectedHexPicNum ; The picture number we expect to be on (used to detect successful searches, etc).
ROM1:1B9C ; (see 1b9c)
ROM1:1B9F beq ActualFrameNumMatchesExpectedFrameNum ; This gets cleared when the expected frame number matches the actual frame number. (see $1BDD)
ROM1:1B9F ; This is used by the disc "search to" test to indicate that an expected frame number was detected by the VBI reader.
ROM1:1BA1 bhi ActualPicNumGreaterThanExpected ; are we off by more than 2 tracks?
ROM1:1BA3 coma ; negate the delta (I am pretty sure, but I haven't tested it)
ROM1:1BA3 ; so it is positive instead of negative
ROM1:1BA5 sbca #$FF
ROM1:1BA7 cmpd #4 ; is delta greater than 4?
ROM1:1BAB bcs SendSkipForwardCommand
ROM1:1BAD ldd #4 ; assume delta is 4 if it is larger than 4 because we only have 4 skip forward commands available to us.
ROM1:1BB0 SendSkipForwardCommand: ; CODE XREF: ThinkActualFrameNumIsExpected+43 j
ROM1:1BB0 pshs b
ROM1:1BB2 addd CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BB2 ; (see 6f3)
ROM1:1BB2 ; Gets 50000 (decimal) stored if VBI frame number appears to be invalid.
ROM1:1BB2 ; (see 6E4)
ROM1:1BB5 std CurrentHexPicNumber ; Adjust current picture number under the assumption that the
ROM1:1BB5 ; track jump will be successful.
ROM1:1BB5 ; We need to track it in case we have to perform multiple track jumps in a row.
ROM1:1BB8 puls b
ROM1:1BBA addb #$C ; send one of 4 PIF 'skip forward' commands,
ROM1:1BBA ; either 0xD, 0xE, 0xF, or 0x10
ROM1:1BBC stb PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1BBC ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1BC1 ; ---------------------------------------------------------------------------
ROM1:1BC1 ActualPicNumGreaterThanExpected: ; CODE XREF: ThinkActualFrameNumIsExpected+39 j
ROM1:1BC1 cmpd #2 ; are we off by more than 2 tracks?
ROM1:1BC5 bcs SendSkipBackwardCommand ; save difference
ROM1:1BC7 ldd #2 ; Assume we are off by 2 if it's more than that.
ROM1:1BC7 ; This is because there are only two reverse PIF commands,
ROM1:1BC7 ; 0x11 and 0x12 so our difference value must be
ROM1:1BC7 ; either 1 or 2 since we add to 0x10 below.
ROM1:1BCA SendSkipBackwardCommand: ; CODE XREF: ThinkActualFrameNumIsExpected+5D j
ROM1:1BCA pshs a,b ; save difference
ROM1:1BCC ldd CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BCC ; (see 6f3)
ROM1:1BCC ; Gets 50000 (decimal) stored if VBI frame number appears to be invalid.
ROM1:1BCC ; (see 6E4)
ROM1:1BCF subd ,s
ROM1:1BD1 std CurrentHexPicNumber ; Adjust current frame number as if the track jump has already occurred and is successful.
ROM1:1BD1 ; We need to track this here in case we need to perform multiple
ROM1:1BD1 ; track jumps in a row.
ROM1:1BD4 puls b,a ; restore difference
ROM1:1BD6 addb #$10 ; this converts the difference value into one of the two PIF 'jump backward' commands (0x11 or 0x12).
ROM1:1BD8 stb PifArray1ByteSpecial ; This byte will be sent to the pif board if it is non-zero.
ROM1:1BD8 ; It is special in that it can receive a higher priority to be sent (I don't fully understand it yet).
ROM1:1BDD ; ---------------------------------------------------------------------------
ROM1:1BDD ActualFrameNumMatchesExpectedFrameNum: ; CODE XREF: ThinkActualFrameNumIsExpected+37 j
ROM1:1BDD clr ExpectedFrameNumStatus ; This gets cleared when the expected frame number matches the actual frame number. (see $1BDD)
ROM1:1BDD ; This is used by the disc "search to" test to indicate that an expected frame number was detected by the VBI reader.
ROM1:1BE0 lda #$F
ROM1:1BE2 sta word_0_A1C0 ; Gets set to 0xF when expected pic number matches actual pic num.
ROM1:1BE2 ; (see 1be2)
ROM1:1BE5 ldd CurrentHexPicNumber ; Stores current laserdisc picture number in hex (converted from BCD).
ROM1:1BE5 ; (see 6f3)
ROM1:1BE5 ; Gets 50000 (decimal) stored if VBI frame number appears to be invalid.
ROM1:1BE5 ; (see 6E4)
ROM1:1BE8 std SearchedHexPicNumber ; Gets picture number after a successful search.
ROM1:1BE8 ; (see 1be8)
ROM1:1BEB clr byte_0_A1B8 ; Gets cleared when expected picture number matches actual picture number
ROM1:1BEB ; (see 1beb)
ROM1:1BEF ; End of function ThinkActualFrameNumIsExpected
Again, this function was quite difficult to understand and took me a long time.
$1B68 checks to see whether it's time to read the real frame number from the video signal.
If it's time, it reads the frame number at $1B86 and converts it from the BCD laserdisc format to regular binary/hex format.
If the frame number cannot be converted from BCD because it contains 'hex' digits, or if it is not a valid VBI picture number, or if it times out, it will try to skip 4 tracks forward and retry ($1B8B).
$1B96 is the routine that actually compares the two frame numbers (actual vs expected). The expected picture number will be subtracted from the actual picture number. If the difference is 0, the search is assumed to be successful ($1BDD). Otherwise, the program will attempt to _skip_ to the right location (which is pretty nifty). It will either skip forward ($1BB0) or backward ($1BCA).
To fix Dexter, I probably need to make sure that I am sending correct picture numbers in the VBI signal a few fields/frames before I send the 'search has finished' signal. This will allow the frame decoding hardware on the VGG board time to get a good read before the ROM program tries to validate whether it is correct. Alternatively, I can make all seeks completely instantly for PR-8210A mode which is probably a lot safer but not as autentic.