Start Contact

How To Examine Or Debug A WPC ROM

Updates:

Index

Introduction

This is my attempt to help "newbies" to understand how a WPC machine works software-wise. You can find a lot of information about it on the newsgroups rec.games.pinball, VPForums.org and the archived Uncle Willy (from Williams), but these are scattered around and sometimes only understandable if you already know something about WPC machines. So this HowTo shall be an introduction to and documentation about WPC software.
This HowTo is based on Indiana Jones L-7, a WPC DCS pinball machine. WPC[-89], WPC-S and WPC-95 should be very similar (see Marvin's WPC Repair Guide). All absolute Game ROM addresses on this page are related to this specific IJ 4 MiBit Game ROM, but should mostly fit to other WPC ROMs of the same size.

Useful Tools

At first you need a tool to extract all the tools and files from archive files like ZIP, RAR and ACE.
For this I recommend the freeware tool 7-Zip. Other freeware tools are listed here.

You need a good hex editor to have a look at the ROM image. Any hex editor with a search function should be sufficient.
I prefer the shareware text editor UltraEdit, as it provides a column text mode and nice search/replace/goto features in hex mode.
As a good freeware text editor I can advise Notepad++ with column selection mode and the Hex Editor plugin.
Other freeware text and hex editors are listed here.

A debug version of PinMAME is also necessary. Before 2.2 you had to compile this version yourself. Nowadays you can just download it.
But if you want to use modified ROMs or features of the latest PinMAME development then you still have to compile it yourself.

For finding strings inside a binary file use SysInternals' Strings, so you "just" have to wade through possible strings and no hex trash.

Some checksum tools are useful too. Those will be mentioned in the corresponding sections.

Last but not least a working brain, which you can put in gear when starting your explorations :)

Getting the PinMAME source

On the initial writing of this page PinMAME was at v1.32.001 and the complete (PinMAMEW) sourcecode was directly downloadable from its homepage, later the source code was available from a CVS (Concurrent Versions System) directory and the whole thing could only be downloaded via a CVS client. Now the source code is maintained inside a Subversion (short: SVN) repository and the source can be downloaded again as a tar ball.
Go to the web interface of PinMAME's Subversion repository. Either browse to a tagged release inside the tags folder or to the latest development inside the trunk folder. Finally click "Download GNU tarball" at the bottom of the page to download the source code. It is recommended to add the directory revision (stated at the top of the page) to the filename after downloading, so the exact revision of the source code is kept for reference.

If you want to get the latest development very often, or if you want to modify the source code on your own, then it is recommend to get the source code via a Subversion client. This allows to update the source code faster by only downloading deltas and, more important, while keeping your local changes. Conflicts between your changes and the official updates will be marked so you can work on them. Last but not least with the client's status and diff commands you can determine what files where changed by you and create a unified diff of your changes.
Clients for Windows are available from Subversion's Binary Packages page. A simple command line client is way enough to handle the PinMAME repository, I recommend Win32Svn's zip package (svn-win32-....zip), just unpack it, add its bin folder to your PATH environment variable and use it. If you need a client GUI for SVN then take a look at pysvn Workbench, TortoiseSVN or RapidSVN.
Also check out the free online eBook Version Control with Subversion for introduction to Subversion usage/handling and reference.
Refer to PinMAME's develop project page on how to check out the source code, but do not forget to add the path you want to check out (trunk or a tagged release).
Example: svn co https://pinmame.svn.sourceforge.net/svnroot/pinmame/tags/release_2_2 .\release22

Compiling PinMAME/32 with MinGW

Follow the instructions in the file "setup_mingw.txt" and create a build that includes the MAME debugger.
To use this build, get the PinMAMEW binary package, extract it and copy your compiled build to its directory.

Compiling PinMAME with Visual C++ (6.0, 2002, 2003, 2005, 2008)

Follow the instructions in the file "setup_vc.txt" and create a build that includes the MAME debugger. If something is not covered there then check out the MSDN forums.
To use this build, get the PinMAMEW binary package, extract it and copy your compiled build to its directory.

There are some important Microsoft Blogs for Visual Studio 2005: general informationen by Scott Guthrie, long installation duration and high diskspace usage plus integrating SP1 into the Setup-CD/DVD (Slipstream) by Heath Stewart. That my adopt to newer versions too.

The Express Editions of Visual Studio are free and can be used for commercial use too (see FAQ).
For Visual C++ 2008 Express Edition download the ISO image of it, which is accessible through the "offline installation" link on the download page. By the way it is not necessary to register, just ignore those messages.

Windows Platform SDK (WinSDK, Windows SDK Blog) (WinSDK was previously called Platform SDK, short PSDK).
The WinSDKs coming with VC++ 6.0, 2002 Pro, 2003 Pro, 2005 Pro and all 2008 (incl. Express Edition) are sufficient to build PinMAME.
Note for Express Edition: You can change between WinSDKs by using the command line with "WindowsSdkVer.exe -version:v6.1" (see here) that sets the WindowsSDKDir variable for VC++ or add the WinSDK pathes to Visual C++ via "Tools → Options" under "VC++ Directories".

DirectX SDK (DXSDK, all downloads) (ToyMaker's DirectX Infos)
Note for Visual C++ 6.0: Add all DXSDK bin, include and library pathes in fullname (no variables) to Visual C++ 6.0 via "Tools → Options" on register "Directories".

Do not forget to update the MSDN Library.
Always install a MSDN Library with information/integration for your Visual Studio version first and then update it with the latest MSDN Library. You can always add the new information of the latest MSDN Library by configuring the Help Collection Manager.
Since Express Editions there are some free MSDN Lib releases: May 2006, April 2007, April 2009 (VS 2008 SP1).
Note for Express Editions: You can only integrate the MSDN Library of the Visual Studio Express Edition you use, but you can always use the latest full MSDN Library as a stand-alone help.

Information I collected to compile the original MAME, not PinMAME, with VC++ 2005 was once available on the VPForums.com site in the compiling PinMAME 1.53 with VC++ 2005 thread (now dead).

Compiling ZLib with Visual C++

My package also provides a project file to compile ZLib version 1.2.3 yourself. To do so check out the included instructions carefully and you need MASM 6.x to compile it with the optimized assembler parts, e.g. from the Processor Pack for Visual C++ 6.0 SP5. After you downloaded the package, extract it to a temporary folder and copy the following files to the BIN folder of Visual C++ or copy them to a separate folder and add its path to Visual C++ via "Tools → Options" under "VC++ Directories":

MASM can not be used for commercial use, see its EULA.
There's also a MASM 8.0 for VC++ 2005, but this does not work with the ZLib 1.2.3 source, so control your VC++ Directories when you have compiling problems with ZLib's assembler sources.
Information and FAQs about MASM are available at masm32.com, the newsgroup comp.lang.asm.x86, Jon Kirwan's homepage and EMS Professional Software.

6809 information, instruction set and tools

The 6809 processor, which is used in WPC pinball machines, is a product of Motorola Microcomputers (became Motorola Semiconductors and is now Freescale as of 2006).
A free programming manual (Code: M6809PM or M6809PM/AD) is still available, check out my htmlized version for all related information.
A copy of the original data sheet is available at the BitSavers.org PDF Document Archive.
The 6809 was produced in several versions. WPC machines use a 68B09EP. The B and E are important. B = certified for 2 MHz, A would be 1.5 MHz, no character behind the 68 is 1 MHz (datasheet). E = external clock, the internal divider is 4 (datasheet, M6809PM section 1.11.2). P = plastic package, L would be ceramic, S would be cerdip (datasheet). An additional C marks a version with a wider temperature range (datasheet). [source: 6809 datasheet, 1, 2]
The 6809 is an 8-bit processor with five 16-bit registers, based on the 6800 family. The meaning of its registers are explained in the programming manual in sections 1.4 to 1.10. Its memory address space is 64 KiB (linear $0000 to $FFFF, 16Bit address). The memory storage format is Big Endian, which means values are stored from HIGHEST byte TO LOWEST byte, e.g #$A607 is stored as #$A6 #$07. The typical 6809 vector locations for reset and interrupts are listed in the M6809PM section 3.1 and in the WPC memory map below.
More information and links regarding the 6809 can be found at the 6809 Emulation Page. For a nice comprehensive overview of the 6809 instructions I suggest to read the 6809 Datsheet at Keith's Home Page (now dead). For in-depth infos from the 6809 developers themselves check out the old Byte magazine articles digitalized by Tim Lidner under Document Reproduction. Wouter's 6809 Page also offers a Motorola Microcomputers MC6809 Course Outline.
The newsgroup comp.sys.m6809 is a good place for 6809 discussions.

Although you will mainly use PinMAME to debug the Game ROM, it is always good to have a disassembler and assembler for the 6809 at hand.
A freeware disassembler is Peter Clare's DASMx (or Pete Clare). The latest version is 1.40 from 2003 on his homepage (version 1.30 from 1999 is available here). A professional, but not free, disassembler is DataRescue's IDA Pro. More on disassembling later in the debugging section.
To compile assembler sourcecode for the 6809 you need an assembler from Motorola or others. As most of you won't do this on a 6809 machine, it has to be a cross assembler. Cross assemblers are compilers that do not run on the environment (CPU and OS respectively) for which they compile the sourcecode for. The free Motorola cross assembler (MOTOASMS.ZIP) and the accompanying documentation (ASREF.ZIP) are no longer available from Motorola's homepage. Fortunately you can find them on any Simtel mirror (see below).
The free cross assemblers of Frank A. Vorstenbosch are listed and available for download on Soft Lookup. The last updates were done in 1999, but that doesn't mean that these are not good. AS09 is the one for 6809 machine code. The free cross assemblers "ASxxxx" of Alan R. Baldwin are available at Shop PDP of the Kent State University in Kent, Ohio, USA.
Most downloads are available on any Simtel mirror: MotoAsms and AsRef, AS09 1.30, ASxxxx Cross Assemblers 4.00.
Also The Coin-Op Cauldron's Laboratory offers downloads of the Motorola tools and DASMx, and R.J.Kuhn's homepage Development Tools for Electronic and Robotic Hobbyists offers the cross assemblers of Frank A. Vorstenbosch.

Modifying WPC ROMs correctly (e.g. for fixing typos and bad translations)

Information and how to create a new correct checksum

Some people slightly modify the Game ROM with a hex editor to correct typos and/or bad translations.

WARNING!!! Using an unofficial/modified Game ROM in your pinball machine voids your warranty and may cause severe damage to it and could even cause fire (e.g. shortcutted coil with +50V)!!! It's your risk, pinball machine and house!!!
Never release your home-made versions to the public, you can mess with your own machine, but not with others and WMS!!!

WPC ROMs are protected against failure and manipulation by a checksum, which is located in the Game ROM itself (offset = <romsize> - 18). Any manipulation changes the actual checksum of the Game ROM, so after modifying you need to calculate a new checksum of the WPC ROM to store within it.
You can use Bill Ung's handy tool RomSum to determine the actual checksum of a binary ROM image, but it can not calculate a new checksum for a modified ROM.
I wrote some small Java classes (=program), which re-calculate a new correct checksum for a modified WPC ROM exactly as Williams did it. The package WPC_Tool14a.zip (Version 1.4a of 2007-09-17) is only 100 KiB in size (incl. sourcecode) and also contains instructions how to use the checksum and calculate it by hand.
While working on a ROM you should set the checksum correction slot (offset = <romsize> - 20) to #$00FF, this will skip the ROM checksum check but also the memory check. Please put a correct checksum in your modified ROM when finished, so you will get alerted of ROM and/or RAM errors.

Adding support of modified ROMs to PinMAME

Instead of burning a modified Game ROM and risking your pinball machine's life, you can test a modified WPC ROM with PinMAME if it already supports the original game. If PinMAME doesn't support the original game, then you can not run any Game ROM version of this game correctly with it, or you have to develop the PinMAME support for this game by yourself :)

WARNING!!! Although an unofficial/modified Game ROM may work fine in PinMAME, this does not mean that it will not cause any damage to your pinball machine (e.g. no real coils in PinMAME)!!!

PinMAME checks the GameROM with a checksum against failure and manipulation too, but it uses its own built-in CRC32 and SHA1 checksums, instead of the WPC checksum in the Game ROM. A great freeware tool for getting those checksums is HashCalc.

To add support for a modified ROM to PinMAME locate the sourcecode files for the game, the code for Indiana Jones is in /src/wpc/sims/wpc/full/ij.c.
First find the section with the ROM definitions and duplicate the definition block of the original ROM version. The block begins with WPC_ROMSTART and ends with WPC_ROMEND. Change the parameters of WPC_ROMSTART to match your modified ROM, first put an "m" behind the second parameter (the version), change the ROM filename accordingly and change the checksums for your modified ROM as the fifth parameter.
Next find the game drivers section with its CORE_GAMEDEF definition and again duplicate the definition of the original ROM version. If the definition starts with CORE_GAMEDEF replace it with CORE_CLONEDEF and repeat the second parameter as an additional third parameter. Change the second parameter as in the WPC_ROMSTART statement. You can also change the description in the fourth parameter if you want.
Open /src/wpc/driver.c and find the DRIVER statement for the game and original ROM version, once again duplicate the entry and change the second parameter as in the WPC_ROMSTART statement.
Recompile PinMAME. Done.

Remember that the Game ROMs have to be in a separate folder or in a ZIP file with normal(!) compression, and that its name has to match the WPC_ROMSTART definition, e.g. "ij_l7m[.zip]" for WPC_ROMSTART(ij,l7m,...).

The WPC DCS machine of "Indiana Jones: The Pinball Adventure"

Technical summary

Pinball Machine Type WPC DCS (DCS stands for Digital Compression System [source])
Processor
Type Motorola Semiconductors 6809 (exactly 68B09EP)
Clock speed 2 MHz, external 8 MHz, internal divider 4
Game ROM
Game Indiana Jones: The Pinball Adventure
Version L-7
Size 4 MiBit (512 KiB, #$00080000, 19Bit address)
Location U2 on CPU board
Pinball OS APPLE, stands for "Advanced Pinball Programming Logic Executive" [sources: PGJ #62 p.22, personally confirmed by Larry DeMar at PE2004, rgp]
wrong is: "Applied Pinball Programming Language Environment" [source], "Advanced Pinball Programming Environment"

Open questions / ToDo:

WPC specialties

A WPC Game ROM has one of the following sizes: 1 MiBit (=128 KiB), 2 MiBit (=256 KiB), 4 MiBit (=512 KiB), 8 MiBit (=1 MiB).

As a Game ROM is several times bigger than the CPU's memory address space of 64 KiB, two tricks [source] are used to access the whole Game ROM:

1. The last 32 KiB of a Game ROM (offset = <romsize> - #$8000) contain the so called "System ROM" and it is always faded into the address range of $8000 to $FFFF. This technique also fills all the reset and IRQ vectors for the CPU to get initialized. On IJ L-7 the reset vector is filled with the start address $8D17 and when the CPU starts then the code at $8D17 gets executed.

2. To access the rest of a Game ROM another technique is used: Paging.
Through paging you can access a ROM in segments, called "banks" in WPC terminology. Banks are selected by setting a hardware register at a special "memory" address. The selected bank is then faded into an area of the normal memory address space and is thereby accessible.
On all WPC pinball machines the paging size is 16 KiB (#$4000). A bank is selected with a byte in $3FFC (see /src/wpc/wpc.h of PinMAME source) and the paging area ranges from $4000 to $7FFF. The WPC ASIC (memory controller) handles this on the hardware side and fakes the CPU accordingly. Depending on the Game ROM size it ignores some bits of the selected bank number, but on the software side these bits should always be set.
The banks are always counted backwards in 16 KiB steps from the end of the file starting with #$3F, no matter what the size of the ROM is. The biggest ROM of 8 MiBit has 64 banks from #$3F down to #$00, 4 MiBit has 32 banks from #$3F down to #$20, 2 MiBit has 16 banks from #$3F down to #$30 and 1 MiBit has 8 banks from #$3F down to #$38. You can see this clearly in the checksum routine on power-up.
Special banks $3F and $3E: Bank #$3F does not exist as bank #$3E contains the 32 KiB big "System ROM" which is always available at $8000.
As the bankswitch selector is used in a linear form, you can calculate the Game ROM offset of a selected bank: (($3FFC) & <mask for romsize>) * #$4000, the mask for 8 MiBit is #$3F, 4 MiBit is #$1F, 2 MiBit is #$0F and 1 MiBit is #$07. There's a table with possible values and corresponding results following.

Note that there's no RAM for the memory space from $4000 to $FFFF, so it's read-only [source].

To get not confused we need "maps" to know what is where, and how the Game ROM, memory addresses and bank selector at $3FFC correspond to each other.

6809 memory map for WPC machines

Adress Content
$0000-$1FFF (8 KiB) RAM
Address Format Description
$0011 Byte Current Bank Marker
$0012 Word Bank Jump Address
$2000-$3FFF (8 KiB) Hardware
Address range Description
$2000-$37FF Expansion (maybe security chip of WPC-S and WPC-95)
$3800-$39FF DMD Page
$3A00-$3BFF DMD Page
$3C00-$3FAF ??? Expansion
$3FBC-$3FBF DMD display control
$3FC0-$3FDF External I/O control
$3FE0-$3FFF WPC I/O control
Address Format Description
$3FFC Byte Bank selector
$4000-$7FFF (16 KiB) Bankswitched ROM (paging area)
$8000-$FFFF (32 KiB) Non-bankswitched "System ROM"
Contains the last 32 KiB of Game ROM
Address Format Description
$FFEC Word Checksum "correction"
$FFEE Word Checksum
$FFEF Byte ROM Version, low byte of the checksum
$FFF0 Word Reserved by Motorola
$FFF2 Word Software Interrupt 3 (SW3) vector
$FFF4 Word Software Interrupt 2 (SW2) vector
$FFF6 Word Fast Interrupt Request (FIRQ), DISABLED on WPC
$FFF8 Word Interrupt Request (IRQ) vector, DISABLED on WPC
$FFFA Word Software Interrupt [1] (SWI) vector
$FFFC Word Non-maskable Interrupt (NMI) vector
$FFFE Word Reset vector
[sources: M6809PM section 3.1, /src/wpc/wpc.h & wpc.c, 1]

WPC bankswitch map (memory location $3FFC)

Value Offset in Game ROM
1 MiBit 2 MiBit 4 MiBit 8 MiBit
#$00 N/A N/A N/A $00000000
#$01 N/A N/A N/A $00004000
#$02 N/A N/A N/A $00008000
#$03 N/A N/A N/A $0000C000
#$04 N/A N/A N/A $00010000
.
.
.
.
.
.
.
.
.
.
#$1F N/A N/A N/A $0007C000
#$20 N/A N/A $00000000 $00080000
#$21 N/A N/A $00004000 $00084000
#$22 N/A N/A $00008000 $00088000
#$23 N/A N/A $0000C000 $0008C000
#$24 N/A N/A $00010000 $00090000
.
.
.
.
.
.
.
.
.
.
#$2F N/A N/A $0003C000 $000BC000
#$30 N/A $00000000 $00040000 $000C0000
#$31 N/A $00004000 $00044000 $000C4000
#$32 N/A $00008000 $00048000 $000C8000
#$33 N/A $0000C000 $0004C000 $000CC000
#$34 N/A $00010000 $00050000 $000D0000
.
.
.
.
.
.
.
.
.
.
#$37 N/A $0001C000 $0005C000 $000DC000
#$38 $00000000 $00020000 $00060000 $000E0000
#$39 $00004000 $00024000 $00064000 $000E4000
#$3A $00008000 $00028000 $00068000 $000E8000
#$3B $0000C000 $0002C000 $0006C000 $000EC000
#$3C $00010000 $00030000 $00070000 $000F0000
#$3D $00014000 $00034000 $00074000 $000F4000
#$3E $00018000 $00038000 $00078000 $000F8000
#$3F $0001C000 $0003C000 $0007C000 $000FC000
Special banks $3F and $3E: Bank #$3F does not exist as bank #$3E contains the 32 KiB big "System ROM".

WPC Game ROM map

Adress Content
$00000000 to (<romsize> - #$8000 - 1) "ROM" (maybe that's the game specific part)
(<romsize> - #$8000) to end "System ROM" (maybe that's APPLE)

Open questions / ToDo:

WPC Debugging And Understanding

General information

Note that all debugging will be done on an unmodified Indiana Jones Game ROM L-7.

As the CPU is a Motorola CPU, the notation for hexadecimal values is $FF unlike Intel's FFh or the C-like 0xFF. Direct values are denoted with a leading # like #$FF, while address for jumps or the value at a address would be just referenced as $FFEC. See M6809PM appendix A.2 for more notation related stuff. Remember that the memory storage format is Big Endian when checking values, which means they're stored from HIGHEST byte TO LOWEST byte, e.g #$A607 is stored as #$A6 #$07.

For debugging a ROM start a command prompt in PinMAMEW's binary directory and type "PINMAMED.EXE <romname> -debug". Use TAB to cycle through the different display areas of the debug window and press F1 to get help about the possible actions in each display area. Always enter "IGNORE 1" in the command area to toggle off sound cpu debugging, otherwise you may get confused when PinMAME switches to the sound emulation and you are suddenly somewhere else. Also enter "SD" to get rid of the annoying sound loops when inside the MAME debugger. You can run the ROM without the debugger by hitting F12 and get back to the debugger with the tilde key (or better said the key left to the "1" key, as international keyboards have the tilde elsewhere).
If the PinMAME emulation, not the debugger, does not accept any keys to trigger switches and so on, then exit PinMAME, delete cfg/<romname>.cfg and restart.
A tutorial about debugging with MAME, NOT PinMAME, is available at MameWorld.net on Unofficial Highscore.dat.

When debugging in PinMAME you only see the CPU memory address space.
If "ROM+<Offset>" is shown you are at $8000 plus offset and see the Game ROM content <romsize> - #$8000 + offset.
If "BANK1+<Offset>" is shown you are at $4000 plus offset, but to find out which bank of the Game ROM is used you have to know the selected bank via $3FFC.

Read the 6809 programming manual (M6809PM) appendix A & D from Motorola Semiconductors mentioned in the previous section, so you know and understand the assembler commands and registers you will see and have to interpret.
And note that a typical optimisation of assemblers is to join a PULL <registers> and a following RTS together to PULL <registers>, PC.

Disassembling

To better understand the examined code it is often useful to have the sourcecode in a text file or printed on paper, for this you need to use a disassembler.
As the Game ROM is much bigger than the 6809 memory the disassembler has to support segments and virtual memory addresses (offset in Game ROM is not the 6809 memory address). If not, you have to split the Game ROM file with your hex editor into each bank and the System ROM part, then disassemble them separately. The Java package WPC_Tool contains a functionality which will split a WPC ROM for you automatically.

DASMx can not map the whole Game ROM file, so you have to use a splitted ROM.
To get started use the DASMx symbol files of my Java package WPC_Tool, which can also analyse any WPC ROM and create DASMx symbol files for it. MC6809.sym contains all hardware vectors of the 6809 processor. The WPC and APPLE symbol files contain all known general definitions for the different versions of WPC hardware and APPLE system.
For Indiana Jones L-7 also add ijone_l7_general_manual.sym to your symbol files pile, as it contains some manually defined symbols for the System ROM part.

IDA supports segments in its own special way: Virtual Address (memory address) = LinearAddress (ROM address) - (Segment Base << 4). The left shifting by 4 equals a multiplication by 16 (2^4), so for example $0A08 becomes $A080. To map the System ROM correctly create a segment from $00078000 to $00080000 (somehow IDA needs for the end address the first byte not in the segment) with a base of $00007000, resulting in $00078000 - ($00007000 << 4) = $8000. The base for the other switched banks can be calculated as following: (Start address - $4000) >> 4. Unfortunately IDA doesn't support negative bases, so you can not map the first bank correctly.

Example: Checksum check (part #1 of powerup code)

Following is the disassembled code of the first part of the startup code which checks the actual Game ROM checksum. Maybe you will find the code here easy to understand, but you have to learn how to interpret the code when you do not have "speaking" labels and explaining comments. Try it yourself by examining the first 100 commands in PinMAME before reading my commented disassembled code.

Initial register values on startup:

PC#$8D17
S#$0000
CC#$50
A#$00
B#$00
X#$0000
Y#$0000
U#$0000

reset_entry:
; Disable IRQ and FIRQ, NMI is always enabled and can't be disabled
	orcc	#$50

; Turn off(?) CPU LED (bit 7)
	ldaa	#$00
	staa	WPC_LED

; Here starts the checksum code
chksum_init:
; Set registers as if all checks were fine
	ldy	#$0006
	clrb
; Compare against special case (for developing, avoids checksum and memory check)
	ldx	GAMEROM_CHKSUM_CORRECTION	; IJ L-7: #$D559
	cmpx	#$00FF
	lbeq	checks_done
; Prepare registers for main checksum loop
	ldu	#$003F	; #$3F banks for a possible 8MBit ROM
	ldd	#$0000	; clear A:B
	exg	d,u
chksum_loop:
; At this point, B contains the next bank to be summed up and U the sum of all previous summed up bytes
	tfr	b,a	; create a working copy of B in A for the next checks
; Check every 8th bank (#$3F, #$37, #$2F, etc.) if WPC ASIC rolled through the ROM and is back at the last Game ROM bank
	coma	; invert A
	bita	#$07	; bit test (AND) of A with %00000111 (correct binary notation?)
	bne	chksum_no_possible_end_of_rom_check

; For checking the bank before this bank will be used, hence A is decreased and the bank selected (#$3E, #$36, #$2E, etc.)
	coma
	deca
	staa	WPC_ROMBANK_SELECTOR
; The first byte of a bank contains the bank number as referenced by this checksum routine (last bank is #$3F and counting down for the previous banks)
	cmpa	BANK_NUMBER_CHECK	; compare A to first byte of bank
	bne	chksum_end	; Doesn't fit, WPC ASIC has rolled, all banks summed up

chksum_no_possible_end_of_rom_check:
; Select the next bank (stored in B), save B in U and get current checksum into D (A:B) for continuing summing up
	stab	WPC_ROMBANK_SELECTOR
	exg	d,u

; The following loop has nothing to do with checksum, as it seems to reset the soundboard 256 times and nothing more, maybe necessary for sound initialization or to keep the soundboard quiet (BONG!)
	ldy	#$0100
chksum_wpc_soundback_loop:
	staa	WPC_SOUNDBACK
	leay	-1,y
	bne	chksum_wpc_soundback_loop

; Finally we add the bytes of the selected bank in a loop
	ldy	#$0006	; has nothing to do with the checksum, but is used for the small WPC Watchdog code inside this loop
	ldx	#BANK	; start address of the bank in 6809 memory
chksum_addbytes_of_bank_loop:
	addb	,x	; adds byte from memory address in X+Offset into B (low byte of D)
	adca	#$00	; adds carry from previous add to A (high byte of D)
	addb	1,x
	adca	#$00
	addb	2,x
	adca	#$00
	addb	3,x
	adca	#$00
	addb	4,x
	adca	#$00
	addb	5,x
	adca	#$00
	addb	6,x
	adca	#$00
	addb	7,x
	adca	#$00
; Small WPC watchdog code which has nothing to do with the checksum. Maybe keeps the watchdog quiet(?)
	exg	y,d
	stab	WPC_WATCHDOG
	exg	y,d
; Back to the checksum code, add 8 to the address in X for the next 8 bytes
	leax	8,x
; Check if X reach the end of the bank (X=start of ROM)
	cmpx	#$8000
	blo	chksum_addbytes_of_bank_loop	; loop as long as it is below $8000 (start of System ROM)
; Save current checksum in U and get selected bank back in B and decrement it for the next loop
	exg	d,u
	decb
	bra	chksum_loop
;
chksum_end:
; Get current checksum back in D and subtract checksum stored in Game ROM from it, if result is 0 it is the same (=good)
	exg	d,u
	subd	GAMEROM_CHKSUM
	beq	chksum_good
	ldab	#$01	; B=1 when bad checksum, otherwise 0 because of SUBD
chksum_good:
	...

Conclusion:
The checksum routine just sums up all bytes of the Game ROM and compares it to the stored checksum within the Game ROM itself. It offers a special branch for developing purposes. During checksum calculation it writes to a WPC sound and watchdog register for unknown reasons.
The routine is programmed for 1MBit ROMs (128 KiB) and multiples of this up to 8MBit (1MB).
Every bank in the Game ROM, except for the last page, has its own page number as its first byte, as referenced by the checksum routine (last page of ROM is #$3F and counting down to the first page of the ROM). This is used to recognize the WPC ASIC rolling through the ROM, so that the Game ROM bytes are only summed up once.

Example: Memory check (part #2 of powerup code)

	tfr	d,y	; backup B in Y (0 or 1)
	ldab	#$06	; again for WPC watchdog

; some initialization; why?
	ldaa	#$B4
	staa	WPC_PROTMEM
	ldaa	#$01
	staa	WPC_PROTMEMCTRL
	staa	WPC_PROTMEM

; fill $0000-$172F with bit sample (first #$55, then #$AA)
	ldaa	#$55
memchk_start:
	ldx	#$0000
memchk_fill_loop:
	staa	,x
	staa	1,x
	staa	2,x
	staa	3,x
	stab	WPC_WATCHDOG
	leax	4,x
	cmpx	#$1730
	blo	memchk_fill_loop	; loop as long as below $1730
; then check if memory shows still the stored sample
	ldx	#$0000
memchk_cmp_loop:
	cmpa	,x
	bne	memchk_bad
	cmpa	1,x
	bne	memchk_bad
	cmpa	2,x
	bne	memchk_bad
	cmpa	3,x
	bne	memchk_bad
	stab	WPC_WATCHDOG
	leax	4,x
	cmpx	#$1730
	blo	memchk_cmp_loop	; loop as long as below $1730
; check for first sample
	cmpa	#$55
	bne	memchk_good
; invert sample for second memchk
	coma
	bra	memchk_start
;
memchk_good:
	tfr	y,d	; get result of checksum check back
	bra	check_results
;
memchk_bad:
	tfr	y,d	; get result of checksum check back
	orab	#$02
;
check_results:
; final test of all checks
	tstb
	beq	checks_done

; let the CPU LED blink
	tfr	d,u	; backup B in U
check_err_led_blink:
; turn LED on
	ldaa	#$80
	staa	WPC_LED
	staa	WPC_SOUNDBACK
;
	ldx	#$FFFF
	ldaa	#$06
check_err_led_on_wait:
	staa	WPC_WATCHDOG
	leax	1,x
	leax	-1,x
	leax	-1,x
	bne	check_err_led_on_wait
; turn LED off
	ldaa	#$00
	staa	WPC_LED
;
	ldx	#$FFFF
	ldaa	#$06
check_err_led_off_wait:
	staa	WPC_WATCHDOG
	leax	1,x
	leax	-1,x
	leax	-1,x
	bne	check_err_led_off_wait
; blink several times, exactly: (%11 both = once, %10 memory error = twice, %01 checksum = once)
; stupid scheme, isn't it? decb/bne would be better, blink the code directly
	lsrb
	bcc	check_err_led_blink

; extra loop, why?
	ldab	#$04
	ldaa	#$06
check_err_extra_wait:
	ldx	#$C000
check_err_extra_wait_inner:
	staa	WPC_WATCHDOG
	staa	WPC_SOUNDBACK
	leax	-1,x
	bne	check_err_extra_wait_inner
	decb
	bne	check_err_extra_wait

	tfr	u,d	; get old B back
	bitb	#$02	; check for memory test error
check_memerr_endless_loop:
	bne	check_memerr_endless_loop	; endless loop if bit is set
checks_done:
	...

Debugging game functions

Most of the time you will be interessted what happens when a special function is started in a game. The best way to do this is to exit the debugger, hit all switches necessary to start the function in the emulation, but get back into the debugger before or directly after you strike the last switch. Use the TRACE command to log what addresses are executed during this function. Unfortunately you can not log the content of a memory address with TRACE, so you don't know the bank of the code from $4000 to $7FFF, but you can search for the bytes of the code in the Game ROM to find out.

The three "call a bank function" routines

Another way to find functions inside a WPC ROM is to debug three routines (I named them "call a bank function" routines) which are in each and every WPC ROM. These routines are used when another function is called. As the target function could be within another bank, these routines care about switching the bank and calling the target function. The JSR routines also care about restoring the previous bank if necessary and jumping back to the original calling code. To find these routines search for the byte sequence "6E 9F 00 12" (=jmp [$0012]) in the System ROM, as this is always the last command of all three routines.

All three routines use a simple general purpose structure I named "Pointer Structure" (PS), this structure is used to point to functions (FPS) and data (DPS):

FormatDescription
wordaddress of target
bytebank of target
#$FF means a System ROM or dynamically selected target

The calls to the three routines are done very tricky with a FPS, or a pointer to one, directly behind the calling JSR, hence totally confusing disassemblers because of mixed code and data. My Java package WPC_Tool can create corresponding symbol files for DASMx, so the disassembler doesn't get confused, but there could also be some false positives (e.g. in graphics).
Other weird FPS constructs are calls of functions in non-existant banks, in IJ L-7 of bank $18 (try PinMAME: WP 0011 18). These FPS really work, as unused conductors, which depend on the ROM size, always have high voltage (=bit set). For IJ L-7 bank $18 leads to bank $38.

Below is "jmpPagedFunc" in hex values and 6809 assembler with comments.

Hex valuesCode  Comment
 jmpPagedFunc:  
34 17  pshs x,b,a,cc ; Push registers (entry values) on System stack (S-5)
AE 65 ldx 5,s ; Load X from S+5:S+6 (S+5 was last System stack value when entering routine = pushed PC of calling code by JSR = address behind JSR)
EC 84 ldd ,x ; Load D from X:X+1 (address of target function)
DD 12 std Bank_Jump_Address ; Store D to $0012:$0013
A6 02 lda 2,x ; Load A from X+2 (bank of target function)
2B 05 bmi jmpPagedFunc_noswitch ; Check if special bank (bank >= #$80 (=negative), #$FF is used for System ROM calls)
97 11 sta Current_Bank ; Store A to $0011 (=real memory, readable)
B7 3F FC sta WPC_RomBank_Selector ; Store A to $3FFC (=hardware register, switches bank through ASIC)
 jmpPagedFunc_noswitch:  
35 17 puls cc,a,b,x ; Restore registers (entry values) from System stack (S+5)
32 62 leas 2,s ; Ignore PC of calling code as this is a bank JUMP (S+2)
6E 9F 00 12 jmp [Bank_Jump_Address] ; Direct-Jump to address in $0012:$0013
 ; End of jmpPagedFunc  

Below is "jsrPagedFunc" in hex values and 6809 assembler with comments.

Hex valuesCode  Comment
 jsrPagedFunc:  
32 7F  leas -1,s ; Reserve additional byte on System stack for current/entry bank on cross-bank calls (S-1)
34 47 pshs u,b,a,cc ; Push registers (entry values) on System stack (S-5)
EE 66 ldu 6,s ; Load U from S+6:S+7 (S+6 was last System stack value when entering routine = pushed PC of calling code by JSR = address behind JSR = FPS address)
37 06 pulu a,b ; Pull A and B (=D) from new User stack U:U+1 (address of target function) (U+2)
DD 12 std Bank_Jump_Address ; Store D to $0012:$0013
A6 C0 lda ,u+ ; Load A from U (bank of target function) (U+1)
2B 22 bmi jsrPagedFunc_noswitch ; Check if special bank (bank >= #$80 (=negative), #$FF is used for System ROM calls)
91 11 cmpa Current_Bank ; Compare with value in $0011 (current bank)
27 1E beq jsrPagedFunc_noswitch ; If equal then directly call function
EF 66 stu 6,s ; Store changed U back to S+6:S+7 (adjusting return address (PC) of calling code on System stack)
D6 11 ldb Current_Bank ; Load B from $0011 (=real memory, readable)
E7 65 stb 5,s ; Store B to S+5 (the reserved byte on the System stack)
97 11 sta Current_Bank ; Store A to $0011 (=real memory, readable)
B7 3F FC sta WPC_RomBank_Selector ; Store A to $3FFC (=hardware register, switches bank through ASIC)
35 47 puls cc,a,b,u ; Restore registers (entry values) from System stack (now pointing to stored entry bank) (S+5)
AD 9F 00 12 jsr [Bank_Jump_Address] ; Subroutine-Jump to address in $0012:$0013
34 03 pshs a,cc ; Push registers (function results #1) on System stack (S-2)
A6 62 lda 2,s ; Load A from S+2 (stored entry bank)
E7 62 stb 2,s ; Store B (function results #2) to S+2 (overwriting stored entry bank)
97 11 sta Current_Bank ; Store A to $0011 (=real memory, readable)
B7 3F FC sta WPC_RomBank_Selector ; Store A to $3FFC (=hardware register, switches bank through ASIC)
35 87 puls cc,a,b,pc ; Pull registers (function results) from System stack and return (pull PC = RTS) (S+3+2)
 jsrPagedFunc_noswitch:  
EF 66 stu 6,S ; Store changed U back to S+6:S+7 (adjusting return address (PC) of calling code on System stack)
35 47 puls cc,a,b,u ; Restore registers (entry values) from System stack (now pointing to reserved byte) (S+5)
32 61 leas 1,s ; Ignore additional byte on System stack (S+1)
6E 9F 00 12 jmp [Bank_Jump_Address] ; Direct-Jump to address in $0012:$0013 (same bank = no bank handling), called function will RTS
 ; End of jsrPagedFunc  

Below is "jsrPagedFuncIndirect" in hex values and 6809 assembler with comments.

Hex valuesCode  Comment
 jsrPagedFuncIndirect:  
32 7F  leas -1,s ; Reserve additional byte on System stack for current/entry bank on cross-bank calls (S-1)
34 57 pshs u,x,b,a,cc ; Push registers (entry values) on System stack (S-7)
EE 68 ldu 8,s ; Load U from S+8:S+9 (S+8 was last System stack value when entering routine = pushed PC of calling code by JSR = address behind JSR)
37 10 pulu x ; Pull X from new User stack U:U+1 (U+2), X now points to a Function Pointer Structure (FPS)
EF 68 stu 8,s ; Store changed U back to S+8:S+9 (adjusting return address (PC) of calling code on System stack)
EC 84 ldd ,x ; Load D from X:X+1 (address of target function)
DD 12 std Bank_Jump_Address ; Store D to $0012:$0013
A6 02 lda 2,x ; Load A from X+2 (bank of target function)
2B 20 bmi jsrPagedFuncIndirect_noswitch ; Check if special bank (bank >= #$80 (=negative), #$FF is used for System ROM calls)
91 11 cmpa Current_Bank ; Compare with value in $0011 (current bank)
27 1C beq jsrPagedFuncIndirect_noswitch ; If equal then directly call function
D6 11 ldb Current_Bank ; Load B from $0011 (=real memory, readable)
E7 67 stb 7,s ; Store B to S+7 (the reserved byte on the System stack)
97 11 sta Current_Bank ; Store A to $0011 (=real memory, readable)
B7 3F FC sta WPC_RomBank_Selector ; Store A to $3FFC (=hardware register, switches bank through ASIC)
35 57 puls cc,a,b,x,u ; Restore registers (entry values) from System stack (now pointing to stored entry bank) (S+7)
AD 9F 00 12 jsr [Bank_Jump_Address] ; Subroutine-Jump to address in $0012:$0013
34 03 pshs a,cc ; Push registers (function results #1) on System stack (S-2)
A6 62 lda 2,s ; Load A from S+2 (stored entry bank)
E7 62 stb 2,s ; Store B (function results #2) to S+2 (overwriting stored entry bank)
97 11 sta Current_Bank ; Store A to $0011 (=real memory, readable)
B7 3F FC sta WPC_RomBank_Selector ; Store A to $3FFC (=hardware register, switches bank through ASIC)
35 87 puls cc,a,b,pc ; Pull registers (function results) from System stack and return (pull PC = RTS) (S+3+2)
 jsrPagedFuncIndirect_noswitch:  
35 57 puls cc,a,b,x,u ; Restore registers (entry values) from System stack (now pointing to reserved byte) (S+7)
32 61 leas 1,s ; Ignore additional byte on System stack (S+1)
6E 9F 00 12 jmp [Bank_Jump_Address] ; Direct-Jump to address in $0012:$0013 (same bank = no bank handling), called function will RTS
 ; End of jsrPagedFuncIndirect  

Open questions / ToDo:

The End?

Of course this is not the end, if you know something about WPC ROMs that is missing or have a correction, more details or just want to help, then do not hesitate and use the contact link below.


Top Start Contact