Start | Contact |
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.
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 :)
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
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.
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).
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":
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 offical version is 1.40 from 2003 (Archives: 1, 2), version 1.30 from 1999 is available here or 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.
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.
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,...).
Pinball Machine Type | WPC DCS (DCS stands for Digital Compression System [source]) | ||||||||
Processor |
|
||||||||
Game ROM |
|
||||||||
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:
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.
Adress | Content | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$0000-$1FFF (8 KiB) | RAM
|
||||||||||||||||||||||||||||||||||||
$2000-$3FFF (8 KiB) | Hardware
|
||||||||||||||||||||||||||||||||||||
$4000-$7FFF (16 KiB) | Bankswitched ROM (paging area) | ||||||||||||||||||||||||||||||||||||
$8000-$FFFF (32 KiB) | Non-bankswitched "System ROM" Contains the last 32 KiB of Game ROM
|
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 |
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:
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.
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.
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.
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: ...
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):
Format | Description |
---|---|
word | address of target |
byte | bank of target #$FF means a System ROM or dynamically selected target |
Below is "jmpPagedFunc" in hex values and 6809 assembler with comments.
Hex values | Code | 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 values | Code | 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 values | Code | 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:
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 |