Monday, December 21, 2015
Wednesday, December 9, 2015
Dexter pre-order period planned to begin on January 1st of 2016
Based on my recent survey and with discussions with multiple potential Dexter customers, I have concluded that at least 50% of people want the Dexter pre-order period to be in 2016 rather than at the end of 2015. Therefore, my plan is to start accepting pre-orders for the first non-prototype version of Dexter on January 1st of 2016. I will be posting more about this at a future time.
Sunday, November 29, 2015
Dexter pre-order poll
When should I take pre-orders for the final version of Dexter? December is a busy time of year, so I am leaning toward January, but maybe December would be better. What do you think?
Vote here .
Results here .
Vote here .
Results here .
Tuesday, November 24, 2015
I've been having email problems.
I've had email problems for at least a month with the email account linked to my Dexter "let me know when it's ready" link on the Dexter web page. So if you've tried to send me an email that way, I probably did not receive it. I'm very sorry for the inconvenience. It should be fixed now!
Thursday, November 19, 2015
Star Rider clever FIRQ handler revealed
In furtherance of my goal to get Star Rider working with Dexter, I decoded the FIRQ handler in the Star Rider main CPU's ROM. FIRQ stands for Fast IRQ and is an alternate interrupt available on the 6809E CPU which is "fast" because it does not push all of the registers on the stack before executing (meaning the programmer will have to assume this responsibility instead but has the option to use the registers cleverly to squeeze out some extra performance).
The Star Rider FIRQ handler is pretty nifty because it is designed to be called when the hardware blitter "special" chips have finished an operation and immediately give the blitter "special" chips more work to do. The neat thing about this design is that the main CPU can still be doing other work while the blitter chips are running. In previous Williams' games, such as Joust, the blitter chip halts the main CPU and no work can be done while the blitter chip is doing its thing. With Star Rider, the hardware guys (apparently) realized that halting the main CPU was a potential waste of time and instead let the main CPU keep running while simultaneously disabling the main CPU's access to the video/DMA bus. It's a really clever design for 1983/1984 or whenever.
Also cool is that the FIRQ handler can be called without an interrupt occurring (see $EAD9). If called in this way, the program will mimic what the CPU does for a real FIRQ so that the RTI (return from interrupt) instruction works correctly regardless of whether there was a real FIRQ or if the program faked it.
Finally, the FIRQ handler is even more nifty because it has an optional "wait for vsync" type behavior built-in. It can optionally check to see whether the next blit will cause visible vertical tearing and if so delay the blit until this is no longer a concern. I am not sure if this code actually ends up getting used in production because, just from eyeballing it, I am not 100% convinced that the vertical counter hardware value matches up with the actual vertical line value (maybe it does!) and the ROM program definitely seems to be making this assumption. Decide for yourself :)
ROM:EAD9 ; =============== S U B R O U T I N E =======================================
ROM:EAD9
ROM:EAD9 ; Execute the FIRQ interrupt handler without the FIRQ interrupt firing.
ROM:EAD9 ; This appears to create an environment where RTI will act like RTS.
ROM:EAD9 ; NOTE: it appears that IRQ's are not disabled
ROM:EAD9
ROM:EAD9 FakeFIRQ: ; CODE XREF: DoBlit:loc_0_EA90 P
ROM:EAD9 ; sub_0_EAB4+18 P
ROM:EAD9 tfr cc, a
ROM:EADB anda #$7F ; '' ; disable E flag (for subsequent RTI call)
ROM:EADD pshs a ; save CC flags (For subsequent RTI call)
ROM:EADF orcc #%1000000 ; disable F flag (for subsequent RTI call)
ROM:EAE1
ROM:EAE1 FIRQ: ; DATA XREF: ROM:FFF6 o
ROM:EAE1 tst PIA_VGG_U7_PERFDIR_A ; clear PIA's IRQA'
ROM:EAE4 dec BlitterPendingOperationsCount ; When FIRQ gets called, it means a blitter operation just finished.
ROM:EAE4 ; So decrement the pending count.
ROM:EAE7 beq FIRQ_End ; branch if we have no more blitter operations left
ROM:EAE9 pshs a,b,x ; save regs (since this is FIRQ, they won't be automatically saved)
ROM:EAEB ldx BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EAEE lda BlitDontAvoidTearing ; 0: try to avoid vsync tearing artifacts
ROM:EAEE ; 1: blit as fast as possible ignoring vertical tearing
ROM:EAEE ; (see EAEE)
ROM:EAF1 bne DoBlitAndAdvanceBlitterContextPtr ; X points to the blitter context
ROM:EAF3 lda 8,x
ROM:EAF5 bmi DoBlitAndAdvanceBlitterContextPtr ; branch if IMPG value has high-bit set
ROM:EAF7 lda 5,x ; A = blitter destination Y value
ROM:EAF9 adda 7,x ; add blit size height to A
ROM:EAF9 ; This makes A now the bottom coordinate of the blit rectangle
ROM:EAFB suba HW_VerticalCounter ; Subtract current vertical counter value from blit bottom Y value.
ROM:EAFB ; If positive, beam is above bottom of blit can tearing can occur.
ROM:EAFB ; If negative, beam is below bottom of blit and tearing is not a risk.
ROM:EAFB ; AT LEAST that's what it is looking like to me! :)
ROM:EAFE bls DoBlitAndAdvanceBlitterContextPtr ; branch if tearing is not a concern
ROM:EB00 ldb #65 ; this appears to create a new blit size
ROM:EB00 ; of up to 64x65 pixels, but can be less.
ROM:EB00 ; It appears to be a way to stall to avoid tearing artifacts on the screen.
ROM:EB02 cmpa #64
ROM:EB04 bcs DoNullBlit ; if width is already less than 64 pixels, branch
ROM:EB06 lda #64 ; force width down to 64 pixels
ROM:EB08
ROM:EB08 DoNullBlit: ; CODE XREF: FakeFIRQ+2B j
ROM:EB08 std HW_BlitterSize
ROM:EB0B inc BlitterPendingOperationsCount ; make sure next time FIRQ fires, the blit we didn't perform gets redone
ROM:EB0E lda #%11000000 ; this appears to execute a NOP to the blitter chips by suppressing all writes. It may be to queue up a future FIRQ.
ROM:EB10 sta HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB10 ; Control Bits
ROM:EB10 ; 7: 1=write suppress odd pixels
ROM:EB10 ; 6: 1=write suppress even pixels
ROM:EB10 ; 5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB10 ; 4: 1=solid color mode (constant substitution)
ROM:EB10 ; 3: 1=zero write suppress (only blit non-zero color values)
ROM:EB10 ; 2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB10 ; 1: write format (0=serial, 1=block)
ROM:EB10 ; 0: read format (0=serial, 1=block)
ROM:EB13 bra FIRQ_RestoreRegsThenEnd
ROM:EB15 ; ---------------------------------------------------------------------------
ROM:EB15
ROM:EB15 DoBlitAndAdvanceBlitterContextPtr: ; CODE XREF: FakeFIRQ+18 j
ROM:EB15 ; FakeFIRQ+1C j ...
ROM:EB15 ldd 8,x ; X points to the blitter context
ROM:EB17 std HW_IMPG_PRIME
ROM:EB1A ldd 6,x
ROM:EB1C std HW_BlitterSize
ROM:EB1F ldd 4,x
ROM:EB21 std HW_BlitterDest
ROM:EB24 ldd 2,x
ROM:EB26 std HW_BlitterSource
ROM:EB29 ldd ,x
ROM:EB2B stb HW_BlitterSolidClr
ROM:EB2E sta HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB2E ; Control Bits
ROM:EB2E ; 7: 1=write suppress odd pixels
ROM:EB2E ; 6: 1=write suppress even pixels
ROM:EB2E ; 5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB2E ; 4: 1=solid color mode (constant substitution)
ROM:EB2E ; 3: 1=zero write suppress (only blit non-zero color values)
ROM:EB2E ; 2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB2E ; 1: write format (0=serial, 1=block)
ROM:EB2E ; 0: read format (0=serial, 1=block)
ROM:EB31 leax 10,x ; advance to the next blitter context in the array
ROM:EB31 ; (each context is 10 bytes long)
ROM:EB33 cmpx #word_0_AC1C ; have we got to the end of the blitter contexts?
ROM:EB36 bcs StoreNewBlitterContextPointer
ROM:EB38 ldx #BlitterContextArray ; a 1000 byte array which has room for 100 10-byte blitter context entries.
ROM:EB3B
ROM:EB3B StoreNewBlitterContextPointer: ; CODE XREF: FakeFIRQ+5D j
ROM:EB3B stx BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EB3E
ROM:EB3E FIRQ_RestoreRegsThenEnd: ; CODE XREF: FakeFIRQ+3A j
ROM:EB3E puls x,b,a
ROM:EB40
ROM:EB40 FIRQ_End: ; CODE XREF: FakeFIRQ+E j
ROM:EB40 rti
ROM:EB40 ; End of function FakeFIRQ
The Star Rider FIRQ handler is pretty nifty because it is designed to be called when the hardware blitter "special" chips have finished an operation and immediately give the blitter "special" chips more work to do. The neat thing about this design is that the main CPU can still be doing other work while the blitter chips are running. In previous Williams' games, such as Joust, the blitter chip halts the main CPU and no work can be done while the blitter chip is doing its thing. With Star Rider, the hardware guys (apparently) realized that halting the main CPU was a potential waste of time and instead let the main CPU keep running while simultaneously disabling the main CPU's access to the video/DMA bus. It's a really clever design for 1983/1984 or whenever.
Also cool is that the FIRQ handler can be called without an interrupt occurring (see $EAD9). If called in this way, the program will mimic what the CPU does for a real FIRQ so that the RTI (return from interrupt) instruction works correctly regardless of whether there was a real FIRQ or if the program faked it.
Finally, the FIRQ handler is even more nifty because it has an optional "wait for vsync" type behavior built-in. It can optionally check to see whether the next blit will cause visible vertical tearing and if so delay the blit until this is no longer a concern. I am not sure if this code actually ends up getting used in production because, just from eyeballing it, I am not 100% convinced that the vertical counter hardware value matches up with the actual vertical line value (maybe it does!) and the ROM program definitely seems to be making this assumption. Decide for yourself :)
ROM:EAD9 ; =============== S U B R O U T I N E =======================================
ROM:EAD9
ROM:EAD9 ; Execute the FIRQ interrupt handler without the FIRQ interrupt firing.
ROM:EAD9 ; This appears to create an environment where RTI will act like RTS.
ROM:EAD9 ; NOTE: it appears that IRQ's are not disabled
ROM:EAD9
ROM:EAD9 FakeFIRQ: ; CODE XREF: DoBlit:loc_0_EA90 P
ROM:EAD9 ; sub_0_EAB4+18 P
ROM:EAD9 tfr cc, a
ROM:EADB anda #$7F ; '' ; disable E flag (for subsequent RTI call)
ROM:EADD pshs a ; save CC flags (For subsequent RTI call)
ROM:EADF orcc #%1000000 ; disable F flag (for subsequent RTI call)
ROM:EAE1
ROM:EAE1 FIRQ: ; DATA XREF: ROM:FFF6 o
ROM:EAE1 tst PIA_VGG_U7_PERFDIR_A ; clear PIA's IRQA'
ROM:EAE4 dec BlitterPendingOperationsCount ; When FIRQ gets called, it means a blitter operation just finished.
ROM:EAE4 ; So decrement the pending count.
ROM:EAE7 beq FIRQ_End ; branch if we have no more blitter operations left
ROM:EAE9 pshs a,b,x ; save regs (since this is FIRQ, they won't be automatically saved)
ROM:EAEB ldx BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EAEE lda BlitDontAvoidTearing ; 0: try to avoid vsync tearing artifacts
ROM:EAEE ; 1: blit as fast as possible ignoring vertical tearing
ROM:EAEE ; (see EAEE)
ROM:EAF1 bne DoBlitAndAdvanceBlitterContextPtr ; X points to the blitter context
ROM:EAF3 lda 8,x
ROM:EAF5 bmi DoBlitAndAdvanceBlitterContextPtr ; branch if IMPG value has high-bit set
ROM:EAF7 lda 5,x ; A = blitter destination Y value
ROM:EAF9 adda 7,x ; add blit size height to A
ROM:EAF9 ; This makes A now the bottom coordinate of the blit rectangle
ROM:EAFB suba HW_VerticalCounter ; Subtract current vertical counter value from blit bottom Y value.
ROM:EAFB ; If positive, beam is above bottom of blit can tearing can occur.
ROM:EAFB ; If negative, beam is below bottom of blit and tearing is not a risk.
ROM:EAFB ; AT LEAST that's what it is looking like to me! :)
ROM:EAFE bls DoBlitAndAdvanceBlitterContextPtr ; branch if tearing is not a concern
ROM:EB00 ldb #65 ; this appears to create a new blit size
ROM:EB00 ; of up to 64x65 pixels, but can be less.
ROM:EB00 ; It appears to be a way to stall to avoid tearing artifacts on the screen.
ROM:EB02 cmpa #64
ROM:EB04 bcs DoNullBlit ; if width is already less than 64 pixels, branch
ROM:EB06 lda #64 ; force width down to 64 pixels
ROM:EB08
ROM:EB08 DoNullBlit: ; CODE XREF: FakeFIRQ+2B j
ROM:EB08 std HW_BlitterSize
ROM:EB0B inc BlitterPendingOperationsCount ; make sure next time FIRQ fires, the blit we didn't perform gets redone
ROM:EB0E lda #%11000000 ; this appears to execute a NOP to the blitter chips by suppressing all writes. It may be to queue up a future FIRQ.
ROM:EB10 sta HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB10 ; Control Bits
ROM:EB10 ; 7: 1=write suppress odd pixels
ROM:EB10 ; 6: 1=write suppress even pixels
ROM:EB10 ; 5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB10 ; 4: 1=solid color mode (constant substitution)
ROM:EB10 ; 3: 1=zero write suppress (only blit non-zero color values)
ROM:EB10 ; 2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB10 ; 1: write format (0=serial, 1=block)
ROM:EB10 ; 0: read format (0=serial, 1=block)
ROM:EB13 bra FIRQ_RestoreRegsThenEnd
ROM:EB15 ; ---------------------------------------------------------------------------
ROM:EB15
ROM:EB15 DoBlitAndAdvanceBlitterContextPtr: ; CODE XREF: FakeFIRQ+18 j
ROM:EB15 ; FakeFIRQ+1C j ...
ROM:EB15 ldd 8,x ; X points to the blitter context
ROM:EB17 std HW_IMPG_PRIME
ROM:EB1A ldd 6,x
ROM:EB1C std HW_BlitterSize
ROM:EB1F ldd 4,x
ROM:EB21 std HW_BlitterDest
ROM:EB24 ldd 2,x
ROM:EB26 std HW_BlitterSource
ROM:EB29 ldd ,x
ROM:EB2B stb HW_BlitterSolidClr
ROM:EB2E sta HW_BlitterControl ; writing here executes the blitter chips.
ROM:EB2E ; Control Bits
ROM:EB2E ; 7: 1=write suppress odd pixels
ROM:EB2E ; 6: 1=write suppress even pixels
ROM:EB2E ; 5: 1=shift image 1 pixel to the right (odd flavor)
ROM:EB2E ; 4: 1=solid color mode (constant substitution)
ROM:EB2E ; 3: 1=zero write suppress (only blit non-zero color values)
ROM:EB2E ; 2: 1=sync to E clock (half speed writes, RAM to RAM)
ROM:EB2E ; 1: write format (0=serial, 1=block)
ROM:EB2E ; 0: read format (0=serial, 1=block)
ROM:EB31 leax 10,x ; advance to the next blitter context in the array
ROM:EB31 ; (each context is 10 bytes long)
ROM:EB33 cmpx #word_0_AC1C ; have we got to the end of the blitter contexts?
ROM:EB36 bcs StoreNewBlitterContextPointer
ROM:EB38 ldx #BlitterContextArray ; a 1000 byte array which has room for 100 10-byte blitter context entries.
ROM:EB3B
ROM:EB3B StoreNewBlitterContextPointer: ; CODE XREF: FakeFIRQ+5D j
ROM:EB3B stx BlitterContextPtr ; points to the current blitter context to be sent to the blitter hardware
ROM:EB3E
ROM:EB3E FIRQ_RestoreRegsThenEnd: ; CODE XREF: FakeFIRQ+3A j
ROM:EB3E puls x,b,a
ROM:EB40
ROM:EB40 FIRQ_End: ; CODE XREF: FakeFIRQ+E j
ROM:EB40 rti
ROM:EB40 ; End of function FakeFIRQ
Monday, November 16, 2015
Dexter Star Rider search bug solution
My birthday party is over (don't think that I mentioned that on here) so I now am turning my focus back to shipping Dexter.
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
ROM3:7E75 ; frame number stored in D
ROM3:7E75 ; on return carry may be set if advance button was pressed
ROM3:7E75
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:7E79 clra
ROM3:7E7A clrb
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 ;
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
ROM3:7E88 WaitSearchFinish: ; CODE XREF: InitiateDiscSearch+21 j
ROM3:7E88 clra
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
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
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
ROM3:7E75 ; frame number stored in D
ROM3:7E75 ; on return carry may be set if advance button was pressed
ROM3:7E75
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:7E79 clra
ROM3:7E7A clrb
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 ;
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
ROM3:7E88 WaitSearchFinish: ; CODE XREF: InitiateDiscSearch+21 j
ROM3:7E88 clra
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
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
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 ;
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
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
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
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 ;
ROM1:1B68 ; This sets Z flag on success (D = 0) and return an error code in D on error.
ROM1:1B68
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:1B76 rts
ROM1:1B77 ; ---------------------------------------------------------------------------
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
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:1B90 clra
ROM1:1B91 rts
ROM1:1B92 ; ---------------------------------------------------------------------------
ROM1:1B92
ROM1:1B92 Error507: ; CODE XREF: ThinkActualFrameNumIsExpected+17 j
ROM1:1B92 ldd #$507 ; this may be an error code
ROM1:1B95 rts
ROM1:1B96 ; ---------------------------------------------------------------------------
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:1BA4 negb
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
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:1BBF clra
ROM1:1BC0 rts
ROM1:1BC1 ; ---------------------------------------------------------------------------
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
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:1BDB clra
ROM1:1BDC rts
ROM1:1BDD ; ---------------------------------------------------------------------------
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:1BEE clra
ROM1:1BEF rts
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).
CONCLUSION:
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.
Monday, October 26, 2015
Dragon's Lair Movie Kickstarter!
If you like laserdisc games (And I am assuming you do since you are reading this), go here and help support this project!
Sunday, September 20, 2015
Dexter Auto-detection of player type working in latest update
Dexter can now auto-detect whether a "Rick Dyer" game is configured for LD-V1000 or PR-7820 mode. These games include Dragon's Lair, Space Ace, and Thayer's Quest.
Thursday, August 27, 2015
Astron Belt (VIP9500SG mode) confirmed working in Dexter!
I just finished up experimental support for the Hitachi VIP9500SG laserdisc player for Dexter. (BTW, I learned that the 'SG' apparently stands for SEGA hehe). Warren tested it out yesterday and it seems to mostly work. There may be a few defects but frankly it's hard to tell what "working correctly" looks like on this game since it's so poorly designed :)
I'll be adding this new firmware update to Dexter Manager soon (it's not quite available).
Tuesday, August 25, 2015
Cliff Hanger confirmed working on Dexter rev 3d!
Cliff Hanger is now confirmed working on the latest Dexter rev 3d board using the infrared interface! This is huge!!! :)
Saturday, August 22, 2015
Seek delay added to Dexter!
See video :)
Saturday, August 15, 2015
Dexter Dragon's Lair 2 bug fixed
Attention Dexter users,
I've fixed a long-standing bug with Dragon's Lair 2 and Dexter where every seek would return with an error. The game can now be played with Dexter (again) although I still have not implemented stop code support, so all scenes will overrun slightly. It's progress! You can update your Dexter devices using Dexter Manager (link on the front of the Dexter home page).
I've fixed a long-standing bug with Dragon's Lair 2 and Dexter where every seek would return with an error. The game can now be played with Dexter (again) although I still have not implemented stop code support, so all scenes will overrun slightly. It's progress! You can update your Dexter devices using Dexter Manager (link on the front of the Dexter home page).
Sunday, August 9, 2015
Dexter HOWTO (updated as of 8 Aug 2015)
New Dexter HOWTO videos
I've been threatening to do these for what, 3 weeks? I finally got them filmed.
Sorry that I speak quietly in some parts. Hopefully I covered everything most people will need to get the most out of their Dexter boards.
I've been threatening to do these for what, 3 weeks? I finally got them filmed.
Sorry that I speak quietly in some parts. Hopefully I covered everything most people will need to get the most out of their Dexter boards.
Wednesday, July 22, 2015
Pre-built Dexter PCBs have arrived!
I was hoping to get these at CAX but it was a day late. They were waiting for me when I got home from CAX.
I've tested them and they are basically everything I had hoped for! I will be shipping these out to people who prepaid for them ASAP.
Here is a video of a quick test:
Thursday, July 9, 2015
How to reverse engineer arcade game hardware
I'll be giving a presentation at CAX again this year. This time it will be about reverse engineering arcade game hardware. I'll share some of the tricks I've learned over the years including how to read schematics, disassemble ROM images, use logic analyzers, etc. I hope to see everyone there ;)
http://caextreme.org/forum/index.php?topic=731.0
http://caextreme.org/forum/index.php?topic=731.0
Monday, June 22, 2015
Reverse engineering Zany Golf (Apple IIgs version) just enough to be able to actually see the whole game :)
So yesterday was Father's Day, and I decided that I wanted to show my kids my old Apple IIgs games that I loved. We pulled up Zany Golf but had a problem: my kids kept losing all their strokes (turns) immediately so they weren't having any fun! I searched online for a cheat so that I could show them the whole game but found nothing.
Challenge accepted!
Tools used: KEGS debugger (setting breakpoints, etc), gdb, and IDA Pro
Why using an emulator is so much nicer: Using an emulator to reverse engineer these old pieces of software is so much more convenient than using a real Apple IIgs. For one thing, it's a lot faster to start the game. And for another things, its built-in debugger is much nicer for stepping through code than trying to hack the boot process of the game enough to do it with the built-in monitor (yuck!).
I decided to search for the message "Player 1, this is your last stroke." because I knew that somewhere near this message would be some logic to check to see what the current stroke count was. And if I knew where the current stroke count was stored, I could set a breakpoint and catch when it gets changed (decremented).
First thing I did was searched for this string on disk. I found it inside the file named "CODE" inside a Zany Golf subdirectory. At this point, I realized that this wasn't very helpful because I did not know how CODE got loaded into memory. I decided that I needed to run Zany Golf in KEGS and search the memory for this string to find where it got loaded.
Unfortunately, I discovered that KEGS (apparently!) does not have a memory search function. After a few false starts down other paths, I decided to whip up a quick n' dirty memory search function that searches the entire emulated memory space for the string I was looking for. On a modern PC, this brute force approach still completes very quickly so it's no big deal.
I tried to understand how KEGS actually managed the emulated memory and I got confused quickly. As grateful as I am to have an emulator for the Apple IIgs, KEGS unfortunately is somewhat convoluted for me (although maybe others don't have as much trouble) with heavy use of global variables that look like local variables, and lots of macros. So I ditched the idea of understanding how KEGS handles its own memory and instead just wrote a new search method that calls the get_memory_c method that KEGS provides and which the debugger uses to do memory dumps. My custom method was tailored specifically to the search string and once I found the string, I wouldn't need it again.
Using this approach, I found the string at $01/736D. On disk, it is at offset $726B inside the CODE file which is kinda weird. This suggests that CODE is loaded at $01/0102. I used IDA Pro to disassemble CODE at this starting address to give me some more visibility. This succeeded with partial success although a lot of the 8/16 bit addressing modes were fouled up.
So the next task was to find when this memory location got read. Since KEGS did not provide a memory breakpoint feature, I decided to use gdb instead. First I had to find out where the native memory location for this emulated memory location was. So I set a breakpoint on get_memory_c and inside KEGS debugger, I type "01/736d" so that only that memory location would grabbed.
I stepped through gdb at this point and after the GET_MEMORY8 macro was called, I typed "print ptr" to see the native memory location.
It came back as 0x7ffff647746d (ie a random number) and this value isn't that important because it may change each time the emulator is run (at least that is my impression). What is important is that I set a memory breakpoint on it and then get the string about my last stroke to appear by playing through the game.
So I typed "rwatch 0x7ffff647746d" and then "continue" to return control to KEGS. I then typed "g" to exit KEGs debugger and resume the game. I played the game until I had one stroke left and sure enough, my memory breakpoint got hit! Woohoo!
'kpc' is the variable name inside KEGS that holds the Program Counter (PC) so I typed "print kpc".
kpc was set to $13e22 (which is $01/3e22). Finally, a starting point to start stepping through the code!
I set a breakpoint on $01/3e20 and restarted the game. But the breakpoint was getting hit too often so I concluded that it was part of the "Draw text" routines. So I disabled the breakpoint, started a new game, and played until right before the message was to appear. Then I entered KEGS debugger again and set the breakpoint.
I putted the ball and sure enough the breakpoint got hit again. I set a new breakpoint on the RTS instruction which came shortly thereafter (because KEGS does not have a way to 'step out' of a function, so this is a poor man's equivalent).
This took me to $01/440C which made the JSR, but this is the beginning of a subroutine (according to IDA Pro) so I concluded that nothing interesting was here. I set breakpoint on 4428 (right before RTS) to step out of this method.
This took me to $01/4437, which is still a small subroutine and did not appear to do any logic to check to see if it's the last stroke. I set breakpoint on $01/4440 to step out of the function.
This took me to $01/6da2 which modifies $28 and $29 so this may be the subroutine that sets the message about it being the last stroke, as I noticed some references to these constants earlier.
I set a breakpoint at $01/6deb, right before the next RTS to step out of this method.
This took me to $01/73cb.
Ah ha! I did a disassembly a little before this point and saw that $01/73b4 does "LDA $424E,X" and "CMP #1" and "BNE $73CB". This is exactly what I've been looking for: something that checks to see if a value is equal to 1. Therefore, $01/424E,X must hold the strokes for the current player!
Looking a little further back, at $01/73b1, I saw LDX $4218 right before the LDA $424E, X ; so $4218 must hold the current player (I assume it would be 0-3).
Returning from this function put me at $01/7473. I saw that the previous call was JSR $7390.
I did not see anything that decrements the memory location jumping out at me so I went back to gdb's data breakpoint on this memory location ($01/424E) using the same technique that I described earlier.
Without even needing to start the game over again, I putted the ball again (which concluded with my game being over) and the memory breakpoint was hit... ($01/705E)
*BOOM*
Here's the code that decrements the stroke count! :)
ROM:7058 LDX word_4218
ROM:705B LDA $424E,X
ROM:705E DEC
ROM:705F STA $424E,X
Looks like a simple NOP at $705E should do the trick to give unlimited strokes.
Using a sector editor, such as Copy II Plus, search for AE 18 42 BD 4E 42 3A 9D 4E 42 (I found it on block $40)
Change the 3A (DEC) to EA (NOP)
You can now show your kids this game without making them get frustrated!! :) :) :)
I've tested through level 2 and have no idea if something REALLY BAD will happen if the stroke count gets too high later, so take that as a caveat. Possible changes to this mod are to hard-code the stroke count to something like 5 instead of NOP'ing out the decrement.
-------------------
UPDATE 23 Oct 2015:
I wanted to try this trick again on a different disk but had forgotten how I hacked KEGS to do a memory search. Here is the function I wrote for reference:
void find_zany()
{
unsigned char array[] = { 0x50, 0x6C }; // "Pl"
// unsigned char array[] = { 0x50, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x20 }; // "Player "
unsigned char cmp[sizeof(array)];
int iBank = 0;
int iAddr = 0;
int i = 0;
// search all reasonable banks where it could be
for (iBank = 0; iBank < 9; iBank++)
{
printf("Checking bank %02x\n", iBank);
for (iAddr = 0; iAddr < (0xFFFF - sizeof(array)); iAddr++)
{
for (i = 0; i < sizeof(array); i++)
{
cmp[i] = get_memory_c((iBank << 16) | (iAddr + i), 0);
}
// if we get a match
if (memcmp(array, cmp, sizeof(array)) == 0)
{
printf("Found a match at bank %02x, addr %04x\n", iBank, iAddr);
}
}
}
}
Inside dis.c, inside the function called do_debug_intfc(), I added this to the switch statement:
case 'k': // zany golf
find_zany();
break;
Subscribe to:
Posts (Atom)