Tuesday, August 15, 2017

Star Rider now working in Dexter (with a ROM mod) !!

TLDR: Star Rider will work with Dexter as it is today if one is willing to modify the Star Rider PIF ROM slightly.  Original EPROM is "AMD AM2732DC", I've successfully used a 2732A.

The Details:

My goal with Dexter is to never make users have to modify their hardware.  I want Dexter to be a drop-in replacement.  However, I don't think I am going to be able to do it in this case without a new revision of the Dexter hardware.  Even then, this modification may also be a wise choice for people who may be using a real PR-8210A laserdisc player and/or arcade operators who don't have time to restart games which are too fussy and crash easily.

Inside the PIF ROM, here is the function that performs a laserdisc search:

ROM:F69B PR8210A_SEARCH:                         ; CODE XREF: ROM:FCBA P
ROM:F69B                                         ; DATA XREF: ROM:F048 o ...
ROM:F69B                 pshs    cc,a,b,x
ROM:F69D                 lda     #3
ROM:F69F                 sta     LastCallbackCmd ; Last callback we executed (only a handful keep track here).
ROM:F69F                                         ; FF: IRQ should do timer test, nothing else
ROM:F69F                                         ; 1: PLAY was called (f5af)
ROM:F69F                                         ; 3: SEARCH callback was called, either by cmd in 0x300 or from FIRQ command 03
ROM:F69F                                         ; 4: jump trigger count does not get cleared after jump trigger is sent (FIRQ cmd 4 received)
ROM:F69F                                         ; 18: No cmd executed yet (or NOP) - IRQ jump triggers disabled, even when on IRQ count 8 (see FD2D)
ROM:F69F                                         ; 1B: disc is in default paused state after IRQ test is finished, jump triggers are active (f564)
ROM:F6A1                 lda     PIAB_PolicyActive ; Holds byte that should be output to PR-8210A PIA port B whenever we aren't in the middle of an operation
ROM:F6A3                 sta     PIA_B_DATA
ROM:F6A6                 orcc    #$10            ; disable IRQ'
ROM:F6A8                 clra
ROM:F6A9                 clrb
ROM:F6AA                 std     SlowTimer       ; This appears to be a timer that increases very slowly (or not at all)
ROM:F6AA                                         ; Can get increased by IRQ
ROM:F6AA                                         ; written by FD68
ROM:F6AC                 clr     UnknownSlowTimerHelper ; gets a variation of what was stored in $43
ROM:F6AE                 std     Cmd04MysteryWord ; may get modified by ChangePlaybackDirection function (F771)
ROM:F6AE                                         ; may have something to do with playback speed
ROM:F6AE                                         ; If high bit is set, playback direction is reverse, otherwise it's forward.
ROM:F6B0                 clr     HowManyJmpTriggersToSend ; This value determines how many jump triggers the IRQ will send at once.
ROM:F6B0                                         ; 0: none
ROM:F6B0                                         ; 1: one
ROM:F6B0                                         ; 2: two
ROM:F6B0                                         ; 3: three
ROM:F6B0                                         ; 4: four
ROM:F6B0                                         ; (no more than 4 is supported)
ROM:F6B2                 clr     HowManyJmpTriggersToSendSrc ; holds jmp trigger count (F78C)
ROM:F6B4                 clr     IsDiscNotPaused ; will be non-zero if PLAY or REJECT command was last issued,
ROM:F6B4                                         ; will be 0 if the STEP FWD/REV (pause) command was last issued.
ROM:F6B6                 ldx     #SearchArrayEnd ; start at memory location 62 (and work backward)
ROM:F6B9                 clr     ,x              ; put 00 terminator at the end so send loop knows when to stop
ROM:F6BB                 lda     #$E8 ; 'F'      ; PR-8210A search command
ROM:F6BD                 sta     ,-x             ; 61 = E8, 60 = E8, 5F = E8
ROM:F6BF                 sta     ,-x
ROM:F6C1                 sta     ,-x
ROM:F6C3                 ldb     #5              ; may indicate 5 digits
ROM:F6C5                 pshs    b
ROM:F6C7                 ldd     [Pointer]       ; load the frame number (unsigned binary format)
ROM:F6CB DigitLoop:                              ; CODE XREF: PR8210A_SEARCH+35 j
ROM:F6CB                 jsr     Pr8210ALoadLeastSigDecimalDigit ; This call computes the least significant frame number digit (in decimal format),
ROM:F6CB                                         ; converts it to the PR-8210A expected format, then loads it into X-1 3 times.
ROM:F6CB                                         ; It also loads a filler byte (0x80).
ROM:F6CB                                         ; X will be equal to where to put the digit + 1
ROM:F6CB                                         ; the frame number will be in D
ROM:F6CE                 dec     ,s              ; the 'b' that we pushed on earlier, subtract it
ROM:F6D0                 bgt     DigitLoop
ROM:F6D2                 leas    1,s             ; pop off digit loop counter
ROM:F6D4                 lda     #$AC ; '¼'      ; PR-8210A FRAME DISPLAY command
ROM:F6D6                 sta     ,-x
ROM:F6D8                 sta     ,-x
ROM:F6DA                 sta     ,-x
ROM:F6DC                 lda     #$E8 ; 'F'      ; PR-8210A SEARCH command
ROM:F6DE                 sta     ,-x
ROM:F6E0                 sta     ,-x
ROM:F6E2                 sta     ,-x
ROM:F6E4                 jsr     Sleep50Ms       ; this delays for 49,993 cycles (from $FA16 until the last RTS has finished)
ROM:F6E4                                         ; So about 50 milliseconds
ROM:F6E7                 lda     ,x+
ROM:F6E9 WhileCmdArrayNotAllSent:                ; CODE XREF: PR8210A_SEARCH+56 j
ROM:F6E9                 jsr     Sleep10Ms       ; this method stalls for 10,004 cycles (from the JSR $FA21 until after RTS has completed)
ROM:F6E9                                         ; So about 10 milliseconds
ROM:F6EC                 jsr     SendPR8210ACmdNoFirq ; Sends two leading 0 bits to remote control line,
ROM:F6EC                                         ; then sends 8 bits stored in A, starting with
ROM:F6EC                                         ; bit 7 and shifting left.
ROM:F6EC                                         ; So for example, the play command would be 0xD0
ROM:F6EC                                         ; (1101 0000)
ROM:F6EC                                         ; FIRQ' is always suppressed, IRQ' may or may not be allowed
ROM:F6EF                 lda     ,x+
ROM:F6F1                 bne     WhileCmdArrayNotAllSent ; if we haven't hit the end (memory location 62) keep going
ROM:F6F3                 jsr     WaitForStandByToStopBlinking ; This waits for the STAND BY line to stop blinking.
ROM:F6F3                                         ; (it will also exit if the STAND BY line is held low)
ROM:F6F3                                         ; If it does not go low initially after about 450 milliseconds, the game will error out with code 0x86.
ROM:F6F3                                         ; 
ROM:F6F6                 ldx     Pointer         ; appears to hold pointer to frame number
ROM:F6F8                 leax    2,x             ; Advance to the next pointer in the FRAME NUMBER lookup table.
ROM:F6F8                                         ; This only seems to apply when SEARCH is called via (currently unknown) callback method.
ROM:F6F8                                         ; If it is called from FIRQ cmd, any change to this pointer gets reverted when this function returns.
ROM:F6FA                 stx     Pointer         ; appears to hold pointer to frame number
ROM:F6FC                 lda     ,s
ROM:F6FE                 tfr     a, cc
ROM:F700                 jsr     SyncIrqCounterToVsyncAndField ; Syncs up IRQ counter with vsync/field
ROM:F703                 andcc   #$EF ; 'n'      ; allow IRQ
ROM:F705                 jsr     DisableLeftAndRightAudio ; Disables left/right audio
ROM:F708                 jsr     PR8210A_PLAY_FIRQ_OK
ROM:F70B                 jsr     PrepareDiscPauseViaJmpTrig ; Sets default behavior to pause the disc using 1 jump trigger (in reverse direction) per track
ROM:F70E                 lda     #1
ROM:F710 WaitUntilIrqCounterIs1:                 ; CODE XREF: PR8210A_SEARCH+77 j
ROM:F710                 cmpa    IRQCounter      ; Counts how many times IRQ handler has been called.
ROM:F710                                         ; When this value is 8, the IRQ handler may issue JUMP TRIGGERS.
ROM:F710                                         ; This gets called every 4 MS approximately, so the JUMP TRIGGERS will occur
ROM:F710                                         ;  once per frame if they happen at all.
ROM:F710                                         ; 
ROM:F712                 bne     WaitUntilIrqCounterIs1
ROM:F714                 puls    x,b,a,cc
ROM:F716                 rts
ROM:F716 ; End of function PR8210A_SEARCH

Notice at $F6A6, IRQs are disabled, and then they are re-enabled at $F703, right after the internal Irq Counter has synced up with vsync and the current field.  The reason that this needs to happen (IMO) is because during searches, there is no sync, and the game relies on being in sync in order to send jump triggers at the right time.  So it makes sense that after a search, the game would need to go through a period of re-sync.

However, this is where things get inconsistent.

ROM:F880 SyncIrqCountToVsyncEnd:                 ; CODE XREF: SyncIrqCounterToVsyncAndField+33 P
ROM:F880                                         ; SyncIrqCounterToVsyncAndField+54 P
ROM:F880                 pshs    cc,a,b,x
ROM:F882                 andcc   #$EF ; 'n'      ; enable IRQ
ROM:F884                 jsr     Wait20msForVsync ; returns carry clear if vsync can be detected within ~20ms or carry set if timeout happens first
ROM:F887                 lbcs    PrepareToReset  ; prepares state to start the ROM program over from the RESET entry point (ie reboot)
ROM:F887                                         ; NOTE : RESET will clobber the log stored at 0x100 and 0x200
ROM:F88B                 orcc    #$10            ; disable IRQ
ROM:F88D                 lda     #$80 ; 'Ç'
ROM:F88F LoopUntilVsyncIsInactive:               ; CODE XREF: SyncIrqCountToVsyncEnd+12 j
ROM:F88F                 bita    PIA_B_DATA      ; read bit 7 from PR-8210A (VSYNC)
ROM:F892                 bne     LoopUntilVsyncIsInactive ; if bit 7 is set, loop until it's clear (ie until vsync signal is inactive)
ROM:F894                 lda     PIA_B_DATA
ROM:F897                 bita    #$40 ; '@'      ; test bit 6 (FIELD) from PIA B port
ROM:F897                                         ; NOTE : field is expected to be up to date when vsync goes inactive
ROM:F899                 bne     TopField        ; if bit 6 is set, it means field is ODD, and to branch
ROM:F899                                         ; NOTE : I think this branch will always be taken because this subroutine only gets called when the bottom field is active.
ROM:F89B                 lda     #4              ; even field
ROM:F89D                 bra     loc_F8A1

This routine will wait 20ms to see a vsync, which seems reasonable since vsync occurs every 16 ms.  However, it also enables IRQs at $F882!!  This means that it is not uncommon for the IRQ handler to be active when a vsync comes in.

Now on a real PR-8210A, the vsync pulse is quite long, so usually, the IRQ handler exits while vsync is still active.  But on Dexter, this vsync pulse is much shorter which means by the time the IRQ handler exits, vsync is no longer active, and the PIF ROM will reboot, starting at $F887.

The 20 ms timeout is really quite aggressive and I can't see any reason for it (I also can't see any reason to enable IRQs right before waiting for vsync).  I suspect that except on a brand new laserdisc player, this aggressive timeout could cause the game to crash needlessly.

My solution?  Increase the 20 ms timeout to something much larger.

ROM:F95E ; returns carry clear if vsync can be detected within ~20ms or carry set if timeout happens first
ROM:F95E Wait20msForVsync:                       ; CODE XREF: RebootIfVsyncIsNotPresent P
ROM:F95E                                         ; ROM:F659 P ...
ROM:F95E                 pshs    a,b,x
ROM:F960                 ldx     #0
ROM:F963 loc_F963:                               ; CODE XREF: Wait20msForVsync+14 j
ROM:F963                 jsr     Wait_122uS      ; Delays about .122 milliseconds (confirmed on real hardware)
ROM:F966                 lda     PIA_B_DATA
ROM:F969                 bita    #$80 ; 'Ç'      ; test VSYNC bit
ROM:F96B                 bne     VsyncDetected   ; branch if vsync is active
ROM:F96D                 leax    1,x
ROM:F96F                 cmpx    #$AA ; '¬'      ; wait 20 ms to see vsync
ROM:F972                 bcs     loc_F963
ROM:F974                 lda     #$94 ; 'ö'
ROM:F976                 jsr     AddToLog        ; This writes a code (stored in A) to a buffer at 0x100, then writes the IRQCounter to a buffer at 0x200.
ROM:F976                                         ; It then appends "END" to the end of the buffer.
ROM:F976                                         ; Subsequent calls will append (ie write to 0x101 instead of 0x100, etc) so that the buffer is actually a log.
ROM:F976                                         ; 
ROM:F976                                         ; I believe the PRE_RESET function checks for a flag that this code writes to RAM and will output it
ROM:F976                                         ; (perhaps via the PR-8210A remote control port?) to an engineer who knew what they were doing could
ROM:F976                                         ; read the log.
ROM:F976                                         ; 0x84: have not received FIRQ command 2 (hello) see f63d
ROM:F976                                         ; 0x89: received FIRQ command 2 (hello) see fca4
ROM:F976                                         ; 0x8B: watchdog counter exceeded limit (IRQ starvation)
ROM:F976                                         ; 0x8D: NMI called (video squelch)
ROM:F976                                         ; 0x8E: REJECT command has succeeded, disc is stopped (not an error) 0xf634
ROM:F976                                         ; 0x90: sanity check failed
ROM:F976                                         ; 0x91: sanity check failed inside IRQ
ROM:F976                                         ; 0x93: $F823 (sync irq counter to field) started
ROM:F976                                         ; 0x94: vsync never detected (f974)
ROM:F976                                         ; 0x95: stand by was raised when it shouldn't have been (f993)
ROM:F976                                         ; 0x96: SWI2 occurred
ROM:F976                                         ; 0x97: SWI3 occurred
ROM:F979                 orcc    #1              ; set carry
ROM:F97B                 bra     IsVsyncPresentReturn
ROM:F97D ; ---------------------------------------------------------------------------
ROM:F97D VsyncDetected:                          ; CODE XREF: Wait20msForVsync+D j
ROM:F97D                 andcc   #$FE ; '¦'      ; clear carry
ROM:F97F IsVsyncPresentReturn:                   ; CODE XREF: Wait20msForVsync+1D j
ROM:F97F                 puls    x,b,a
ROM:F981                 rts
ROM:F981 ; End of function Wait20msForVsync

I changed $F96F so instead of waiting until the counter (stored in X) reaches #$AA, I wait until it reaches #$3FF.  Why #$3FF?  This choice is somewhat arbitrary.  I just picked a value that covers many fields instead of just 1 field.

This change did the trick and now Dexter seems to be working perfectly with Star Rider!  Woohoo!

For those curious, there is a function called at $F963 which sleeps for about .122ms and #$AA is 170 in decimal, and 170 * 0.122 is 20.74 which is where I calculated that this method waits about 20 ms for vsync to appear.

The change to make to the PIF ROM to increase this timeout value:
  • Change address 0x005 from #$F2 to #$4A .  This makes it so the internal checksum test still passes.
  • Change address 0x970 and 0x971 from 0x00 0xAA to 0x03 0xFF

No comments:

Post a Comment