Friday, May 8, 2026

Reverse engineering Halcyon's laserdisc player command sending routines inside the Z80 ROM

When I reverse engineer laserdisc arcade game ROMs, one of my favorite things to do is find/disassemble the routines that send commands to the laserdisc players. Every laserdisc game will have these somewhere in the ROM so it's a "comfortable" place to start reverse engineering because the commands for the player are already known.
 
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*

Wednesday, February 18, 2026

Halcyon working with real laserdisc player!

 I had uploaded this video back on Apr 2025, but I guess I forgot to post about it here on my blog.  So here it is.



Thursday, June 27, 2024

Halcyon replacement power supply finished

 Since I will need to return the original power supply soon, I needed to whip up a replacement one so I could keep working on Halcyon once I've returned the original.  Here's my quick n' dirty solution that seems to work.




Saturday, June 22, 2024

Halcyon v2 repair log - RAM not working correctly

So I've got far enough with my Halcyon clone project to get the CPU working properly.  I wrote a small test program to test RAM I/O and also that the second EPROM (U32) can be read properly.  Right off the bat, my test program reported a RAM test failure.

After doing some digging, I isolated the problem to pin 20 of the RAM chip not having the proper timing.  This pin is driven by pin 8 of U18, as shown here:


A logic analyzer capture of a looping RAM read/write operation revealed the following on my clone PCB:


Pin 8 was starting and ending late.  You can compare this to the original Halcyon where pin 8 much more responsive:

Being utterly confused, I decided to hook up my scope to get a better view of the problem and saw this:
Finally, I looked again at the schematic and noticed that there is a capacitor attached to this line. Doh!

The problem ended up being that C39 needs to be a 0.001uF cap, not a 0.01uF cap.  Once I replaced the cap with the proper value, things started working.

Friday, June 7, 2024

Esh's Aurunmilla hardware is now open source!

I've sold all of my Esh's clones and commercial demand is exhausted.  So I've open sourced everything I created to make an Esh's PCB clone.  You can get it here: https://github.com/rulecity/EshsAurunmilla


Wednesday, March 13, 2024

Halcyon clone v2 repair log

  •  Wrote test ROM, then hooked up logic analyzer to Z80 to see how far it got.  Was surprised to see that RESET' was held low.  Using logic probe, looked at U45 which is a flip flop that controls the reset circuit and saw that VRESET' was low despite WRESET' being high.  Hooked up logic analyzer to U45 and saw that pin 13 (flip flop reset) pulses low shortly after WRESET' goes high, which seems like it shouldn't be happening.
  • Hooked up logic analyzer to U44 and saw that pin 8 (which is supposed to be an output) is mirroring pins 12 (an input that takes in a 4 MHz clock) and 11 (an output that takes the 4Mhz clock to the MC6803P microcontroller).  Pin 8 seems to be completely ignoring its inputs, pins 9 and 10.  The Halcyon uses a 74HC08 chip for U44 instead of a 74LS08 (which I am using for my prototype) so I'm going to try the 74HC08 to see if it makes a difference.
  • Replacing U44 with 74HC08 fixed this problem!

Tuesday, February 13, 2024

v1 of Halcyon clone ready for testing

 It most likely has defects, but it's good enough for me to get some of these fabricated and start testing it.



Monday, January 15, 2024

COP421 reader finished!

 I finished building my COP421 reader and it seems to work perfectly.

I confirmed that the Halcyon COP421 program is identical to the dump that Mark Broadhead made over 20 years ago of the arcade Thayer's Quest COP421 program.

I uploaded everything (source code, CAD files, etc) to github, which you can find here: https://github.com/rulecity/COP421Reader/



Saturday, January 13, 2024

Building a COP421 reader

 

Just to be sure I get every last detail that I can from the Halcyon that I have on loan, I've decided to follow the excellent instructions from here ( http://john.ccac.rwth-aachen.de:8000/patrick/COPSreader.htm ) and create my own COP reader to read Halcyon's COP chip.  Now, I am 90% sure it will be the same one as on Thayer's Quest arcade, but I need to make 100% sure.  I could use the same device as in the link, but I think it will be more fun/challenging to create my own.  I am re-using some parts that I've already used with Dexter since I'm more familiar with them (mainly the FT232 USB IC and the ATMega644p microcontroller at 18.432 MHz).

Thursday, March 23, 2023

Building a Halcyon cartridge reader

 Using the Thayer's Quest arcade schematics and the Thayer's Quest arcade 'cartridge', I made a simple PCB adapter so that the cartridge could be read in an EPROM reader.


First test was the arcade 'cartridge' to make sure I didn't damage anything.


After that worked, I tried the actual Thayer's Quest Halcyon cartridge.



Conclusion?  It works!


Monday, March 6, 2023

Bega's Battle - Sync Generator Replacement - Testing with Super Don NTSC decoder

 

I don't have a Data East chroma PCB so I decided to try rigging up a Super Don NTSC decoder.  The colors are a little off, but it works well enough for testing!









Thursday, February 16, 2023

Bega's Battle Sync Generator Replacement - How to install

My Bega's Battle / Cobra Command / Road Blaster mod to allow Dexter and other Sony laserdisc players to work with these laserdisc games is almost complete!  Here is the video I've created that shows how to perform the mod.

Sunday, January 15, 2023

My Migraine Remedy

Do you hate migraines?  Do you have trouble getting rid of them once they come?  Here’s my "shotgun" recipe to kill migraines:

- take two Excedrin Migraine pills

- drink one caffeinated beverage* (I use Diet Mountain Dew)

- do about 30 minutes of cardio where your heart rate is in the “green” zone (70-80% max heart rate), drinking lots of water.  Migraine will slowly go away the longer you exercise.

- go to bed early and get extra sleep

Sometimes not all of these steps are necessary, but doing all of them almost always works for me.

* - Caveat: I avoid caffeinated beverages almost always _except_ when I am applying this remedy.  If you drink caffeinated beverages regularly, your results may be different. 

Wednesday, October 5, 2022

Cobra Command (dedicated) confirmed working with Dexter!

 Very exciting progress to show here.


Thanks to Mike Treu for testing for me!

By the way, not only will this mod work with Dexter, but it will also work with the Sony LDP-1450 which Mike also tested.

The mod replaces 16K on the bottom Data East board with a custom PCB that I designed.  So unfortunately, it will require modifying the game hardware, which I have tried to avoid as much as possible but which was ultimately the only feasible solution I could find for these games (Bega's Battle, Cobra Command and Road Blaster).

After the mod is installed, the game will work with:

  • The original Sony LDP-1000A (yes, I maintained backward compatibility)
  • Dexter
  • Sony LDP-1450 and probably most other Sony players.

Wednesday, September 21, 2022

Saturday, September 17, 2022

How to kill ants

So, I was using peanut butter as bait for mouse traps in my garage, but ants kept coming and stealing it.  I find it rather ironic that I had to get rid of a second pest (ants) in order to deal with the pest I originally was targeting (mice).

Here's an ant bait recipe I took from another website and modified:

  • 1 teaspoon Peanut Butter
  • 2 teaspoons Borax (yes, the stuff used as a household cleaner; this stuff is the ant poison)
  • A small amount of vegetable oil
Mix the peanut butter and borax together as best as you can.  Add small amounts of vegetable oil until consistency is like the peanut butter was before you added the borax.  This is important to keep the bait from drying out (ants will ignore it if it dries out).  Place bait in the place you see ants (or where ants are known to come steal your mouse bait!!).  Do not interfere with the ants as they take the bait back to their queen.  This will take about 4 days.  Afterwards, the entire colony will be dead.

Day 1



Day 2



Day 3


Day 4



Friday, September 9, 2022

Making a MC44144 tester for Cobra Command / Dexter research

I've pondered about whether the "obsolete" MC44144 chip could be useful for making Bega's Battle (and Cobra Command dedicated) work with Dexter.  Although it isn't in production anymore, from what I can tell, it's still fairly obtainable.

Here's the PCB I've designed to help test it and see how well it works.



Friday, March 18, 2022

Cobra Command wouldn't boot

I've been trying to get my Cobra Command boardset (Bega's Battle conversion) up and running.  I have no official wiring harness, so I've been making my own wiring + PCB adapters, including power, serial, and RGB video.  When I tried powering up Cobra Command and plugging it into Dexter I got nothing.

So I did what anyone who loves tedious troubleshooting work would do.  I sniffed the 6502 CPU to see where the program was getting stuck.


I found that the program was looping endlessly at a subroutine at 0xE176:


But I wanted to know what was calling this subroutine.  Through some clever triggering, I was able to narrow it down to this area of code:



So basically, the game is sending the "clear all" command to the laserdisc player and blocking indefinitely until it gets a response (which may never happen).

Conclusion?  Serial I/O isn't making it from the game to Dexter.

After some more poking around, the answer ended up being quite simple.


The game is communicating at 1200 bps!  I've never seen a game communicate this slowly to the laserdisc player!

I was getting tripped up because I was assuming 9600 bps and even after staring at the logic analyzer capture and seeing traffic, my brain just didn't make the connection that maybe everything on the game PCB was working correctly and I just was assuming the incorrect clock rate!