(Topic ID: 261103)

Stern Galaxy code disassembly/Modifications

By slochar

4 years ago


Topic Heartbeat

Topic Stats

  • 175 posts
  • 22 Pinsiders participating
  • Latest reply 1 year ago by slochar
  • Topic is favorited by 37 Pinsiders

You

Linked Games

  • Galaxy Stern Electronics, 1980

Topic Gallery

View topic image gallery

pasted_image (resized).png
20220522-ASRERROR (resized).png
20220521-BACKWARDS (resized).png
20220520-CREDIT5 (resized).png
20220520-CREDIT4 (resized).png
20220520-CREDIT3 (resized).png
20220520-CREDIT2 (resized).png
20220520-FREEPLAY (resized).png
20220520-CREDIT (resized).png
8-29-sound7 (resized).png
8-28-sound6 (resized).png
8-28-sound5 (resized).png
8-28-sound4 (resized).png
8-28-sound3 (resized).png
8-28-sound2 (resized).png
8-28-sound1 (resized).png
There are 175 posts in this topic. You are on page 1 of 4.
17
#1 4 years ago

Based on the results of the poll ran a couple weeks ago, Galaxy will be the next mpu200 game to be disassembled, and I'll be sharing what I'm doing step by step as best as I can so others can do this as well if they wish.

Step 1: Dump the code from pinmame to a text file.

Start pinmame in debug mode and type DASM GALAXY.ASM 1000 5FFF

This will disassemble the binary code to a file called GALAXY.ASM, from 1000 (U1 rom start) to the end of U6 ($5FFF). The memory addresses that the roms are in are:
U1: $1000-$17FF
U2: $5000-$57FF
U5: $1800-$1FFF
U6: $5800-$5FFF

This memory map is true for all stern mpu200 games that use 2k roms in all 4 locations 1, 2, 5, 6.

You might as well start a memory map for your game at this time as well, so create a text file loosely in the format:

MEMORY MAP

1000-17FF U1
1800-1FFFF U5
5000-57FF U2
5800-5FFF U6

I'll add here that the way I do all of this is just the way that happens to work for me. Others undoubtedly have their own methods or will develop their own methods as they go along.
STEP1 (resized).pngSTEP1 (resized).png

#2 4 years ago

Step 2: Save the original binary file for later comparison purposes.

SAVE GALAXY.ORG 1000 5FFF

STEP2 (resized).pngSTEP2 (resized).png
#3 4 years ago

Step 3: Open the Galaxy.asm file in your text editor of choice. I use Notepad++, freeware available at: https://notepad-plus-plus.org/

The resulting file will need a lot of fixing. I'm not going to paste the entire file here, but you can get it from:
https://sites.google.com/site/allentownpinball/galaxy-asm

Here is a small sample that has both 6800 assembly language and what looks like a bunch of scrambled code interspersed:

L136D:
; db $26
; db $07
bne $1376
L136F:
; db $DE
; db $22
ldx $0022
L1371:
; db $26
; db $03
bne $1376
L1373:
; db $7E
; db $18
; db $EB
jmp $18EB
L1376:
; db $3F
swi
L1377:
; db $18
illegal
L1378:
; db $1B
aba
L1379:
; db $32
pula
L137A:
; db $4A
deca
L137B:
; db $4B
illegal
L137C:
; db $48
asla
L137D:
; db $19
daa

#4 4 years ago

Step 4: Massage the code to get something that can compile. Leverage knowledge of previous disassemblies to make sense of the code.

Sub steps:

Use find and replace in notepad++ to remove extraneous spaces.
Change all branch functions from JMP $xxxx to JMP Lxxxx. The branching functions in the 6800 are:

BCC, BCS, BEQ, BGE, BGT, BHI, BLE, BLS, BLT (mmmm.... bacon), BMI, BNE, BPL, BRA, BSR, BVC, BVS, JMP, and JSR.

So BCC $ becomes BCC L, BLT $: BLT L, etc.

Some of the instructions are indexed instructions, of the format jmp $00,x. You want these instructions to stay as jmp $00,x, so after you do the above substitutions, change the L00,x to $0,x instead.

Referring back to the code snippet above, anything preceded by a semi colon ; is a comment. I use a customized version of pinmame that spits out the object code in the format you see above which is essential to the manual process that will be occurring soon.

I'll also address something in the way I'm going to post this tutorial - assembly mnemonics can be upper or lower case, however, conventional practices dictate that labels are upper case, and mnemonics are lower case. To make the mnemonics stand out I'll capitalize them in the thread, but you should always write them as lower case. Macros can be upper or lower case and most of the time I use lower case for those as well, but sometimes upper depending on what it is.

#5 4 years ago

Sub step, continued:

Here is what the snippet above looks like once it's been converted from object/disassembled code into what we're looking for in the final product:
bne L1376
ldx DIPS_17_24 ;$22
bne L1376
jmp L18EB ;all dips off, do burn in/self-test

L1376:
swi
rcflagset
lampbvalueon $32 ;game over
show_credits
show_hstd
update_match

ila

Everything after the SWI (software interrupt) is a macro, from a file created to make assembly of the final product easier. The macro file I created is here:
https://sites.google.com/site/allentownpinball/galaxy-asm as file 'stern13.asm'.

At the top of your assembly file, add an assembler directive to use this macro file:

include "stern13.asm"

include.pnginclude.png
#6 4 years ago

The beginning of the code sure doesn't look like anything that makes sense.... a bunch of ora $0013.

This is a pointer table that at this point we don't know what it points to. Let's leave this alone for now and take a look at some other clues to get us going.

functiontable.pngfunctiontable.png
#7 4 years ago

Look at the end of your disassembled file. The locations $5FF8-$5FFF contain the 6800 processor vectors for IRQ, SWI, NMI, and RESET.

Because of the way the disassembler in pinmame works, it's trying to make 6800 code out of what are pointers.

L5FF7:
; db $FF
; db $12
; db $4B
stx $124B
L5FFA:
; db $10
sba
L5FFB:
; db $B2
; db $12
; db $46
sbca $1246
L5FFE:
; db $1B
aba
L5FFF:
; db $74
; db $00
; db $00
lsr $0000

Starting at 5ff8, change the code to pointers instead, and add some comments:

org $5ff8 ;start of 6800 vectors
dw L124B ;irq - interrupt request
dw L10B2 ;swi - software interrupt
dw L1246 ;nmi - non-maskable interrupt
dw L1B74 ;reset - address to jump to on power-up of 6800

I've added the "ORG" assembler directive to indicate that these 4 data words (DW) need to be at the location $5ff8. (Anything addressed on an mpu200 board as U6 $5800-$5FFF also appears to the processor as the addresses $F800-$FFFF as well. This is a requirement of the 6800 processor, the vectors must be available at $FFF8-$FFFF on bootup.)

Since the file should start at $1000, add an "org $1000" to the top of your file as well.

vectors.pngvectors.png
#8 4 years ago

You can follow any of the 6800 pointer vectors to decipher each of what they are doing.

I'm going to start with NMI (non-maskable interrupt - this is an interrupt that the 6800 cannot block, and if you refer to the schematic snippet attached, you can see it is attached to S33, which is the 'clear' switch on the mpu board).

We know from experience/reading the manual that this is the switch you press when you want to clear (zero out) an audit or score adjustment. So, we already know what this NMI function is supposed to do.

The pointer for NMI points to L1246:
L1246:
; db $86
; db $FF
lda #$FF
L1248:
; db $97
; db $60
sta $0060
L124A:
; db $3B
rti

Cleaned up and commented:
;NMI
L1246:
lda #$ff ;set nmi flag
sta $60 ;save nmi flag
rti ;return from interrupt

There's not a lot to this routine, and I've made an assumption that there's more to it that meets the eye, which is why I added comments the way I did. Something else in the code must react to this flag we've set.

Make a note on your memory map file that location 0060 is the NMI flag.
The memory map so far looks like this:

MEMORY MAP
====== ===

0060 NMI FLAG

1000-17FF U1
1800-1FFF U5
5000-57FF U2
5800-5FFF U6

F800-FFFF U6 (DUPLICATE)

nmi.pngnmi.png
#9 4 years ago

Returning to the 6800 vectors, let's look at the SWI (Software interrupt) vector next. A software interrupt performs the same function as a hardware interrupt (either the previously decoded NMI and still to be decoded IRQ) but under control of your program. Anytime an interrupt happens, regardless of if it's hardware generated or software generated, the following happens:

The processor finishes the current instruction it's on.
The processor registers and return address are pushed onto the stack.
The processor looks at the vector location corresponding to the interrupt. (software interrupt is $FFFA-FFFB)
The processor jumps to the interrupt pointed to by the vector and runs the code there until it encounters either another interrupt or an RTI (return from interrupt) instruction.

This pointer is located at $10B2, original dump below. An idiosyncrasy of the disassembler has merged whatever 6800 code is actually at 10b2, so within pinmame, you can use the J <address> command to show the code for cleanup. $4f is "clra".
L10B1:
; db $D7
; db $4F
stb $004F
~~ (snipped for brevity)
L10D7:
; db $32
pula
L10D8:
; db $3B
rti

Change the code to add the missing L10B2 label and correct the source.
L10B1:
; db $D7

;SWI software interrupt
L10B2:
clra ;clear A
L10B3:
; db $36
psha ;save A

Here is the entire software interrupt routine before commenting:
;SWI software interrupt
L10B2:
clra ;clear A
psha ;save A
tsx
lda $00,x
tap
L10B8:
ldx $06,x
lda $00,x
asla
psha
lda #$10
adca #$00
psha
tsx
inc $09,x
bne L10CA
inc $08,x
L10CA:
ldx $00,x
ldx $00,x
ins
pula
jsr 0,x
L10D2:
tsx
lda $00,x
bne L10B8
L10D7:
pula
rti

I've removed labels and extra object code comments for readability's sake.

#10 4 years ago

There are a LOT of instances in the disassembly where a software interrupt instruction occurs, and then the following code doesn't seem to make sense.

Example:
L1376:
; db $3F
swi
L1377:
; db $18
illegal
L1378:
; db $1B
aba

Clearly, that instruction at $1377 is not going to recompile correctly. This gives us a clue as to what the software interrupt is doing - maybe it's reacting to the $18 as something other than either data, or as a 6800 instruction (since it isn't one).

As a side note, this is where in 2004-2007 I stopped trying to figure out what was happening in the coding. I didn't have the knowledge at that time to realize that the software interrupt on a stern game starts an interpreted byte code decoding engine that controlled the game. If you think of the stern byte code as a higher [than assembly] level language, it stands to reason that all higher level languages either have to be compiled to machine code (one step below assembly) or have to be interpreted somehow.

Since the code following the SWI isn't a valid instruction, it must be an interpreted language. Following that assumption, the software interrupt might be the interpreter for that language. This is correct; the stern interpreted byte code language turns out to be called PIGS, which stands for "pinball interpreted game system". The original author, Alan McNeil, used to have a webpage with his CV listing this, but it seems to have been moved or defunct.

Over the next several days/weeks/months/years I'll be taking apart and commenting the code to Galaxy, a little bit at a time, so that others can either be inspired to do the same, or just curious as to what's going on, and can follow along.

Next up will be the comments for the SWI routine; I'm pretty much going to abandon the "what does this do" format I've used previously, as I pretty much already know what each set of 'random' data does already in a stern mpu200 game due to this being the 20th or so time I've done this. Hopefully the comments I add will clarify what's going on in the code.

#11 4 years ago

Here's the commented software interrupt. Further explanations follow the code.

;SWI software interrupt
L10B2:
clra ;setup marker byte
psha ;save
tsx ;get the stack pointer
lda $00,x ;get the saved condition register for the current data silo
tap ;move the saved condition register to the live condition register
L10B8:
ldx $06,x ;get the current script pointer from the data silo
lda $00,x ;get the next pigs command from the current script
asla ;double it for indexing
psha ;save the doubled command which will become the LSB of the pigs table pointer
lda #$10 ;PIGS table starts at $1000
adca #$00 ;handle any carries (there shouldn't be any as there are only 58 pigs commands)
psha ;save the MSB of the pigs table pointer
tsx ;get the stack pointer back into X
inc $09,x ;increment the script pointer LSB
bne L10CA ;no carries, branch
inc $08,x ;script pointer LSB went from $ff to $00 - increment script pointer MSB
L10CA:
ldx $00,x ;get the newly-formed pigs table pointer into X
ldx $00,x ;get the routine to run pointer into X
ins ;move the stack pointer up to ignore the PIGS table pointer MSB
pula ;get the doubled PIGS command back into A
jsr 0,x ;execute the PIGS command
L10D2:
tsx ;get the stack pointer for this data silo back
lda $00,x ;get the multiprocessing flag
bne L10B8 ;PIGS multiprocessing flag is set, keep processing PIGS commands
L10D7:
pula ;get rid of the marker byte
rti ;return from interrupt

What the software interrupt is doing is getting a PIGS command from the script referenced by a data silo, determining where the command should be executed in a lookup table, advancing the script, then executing the command.

The PIGS command table index resides at $1000. (Now we know what all that data that started out with ora $0013 is.....) This is deduced by the pushing of #$10 into the stack to form the address of the pigs function.

Next up we'll look at each function in the pigs table.

#12 4 years ago

Next up, the PIGS function table, from $1000-$10B1. This range obtained from the SWI code (the starting point, a PIGS command of $00 would return a pointer address of $1000, so that's the start) and $10B1 as $58 PIGS commands*2 for the upper range. It is very handy to have a programmer's calculator open when doing this (one comes with Windows 7 and likely other versions of windows).

If you want, you can add to your memory map '1000-10B1 PIGS function table'. I usually skip what the program itself is doing in the memory map and reserve the memory map for actual memory location use.

The function table for Galaxy is obtained by changing all the object code in the disassembly from this:
L1000:
; db $13
asx2 (s+1)
L1001:
; db $9A
; db $13
ora $0013
L1003:
; db $9A
; db $13
ora $0013
L1005:
; db $9A
; db $13
ora $0013
L1007:
; db $9A
; db $13
ora $0013

To this:
L1000:
dw L139A ;function $00
dw L139A ;function $01
dw L139A ;function $02
etc.

Referring back to the original code that set us off on the SWI trip:
L1376:
; db $3F
swi
L1377:
; db $18
illegal
L1378:
; db $1B
aba

We now know that SWI starts the PIGS engine, and that $18 must be a pigs command.

Offset $30 in the pigs table (that's $18*2) is:
dw L10D9 ;$18

The code for function $18 resides at $10d9:

L10D9:
; db $86
; db $FF
lda #$FF
L10DB:
; db $30
tsx
L10DC:
; db $A7
; db $02
sta $02,x
L10DE:
; db $39
rts

This code is saving value #$ff in the current data silo. Referring back to the SWI engine, specifically this section:
L10D2:
tsx ;get the stack pointer for this data silo back
lda $00,x ;get the multiprocessing flag
bne L10B8 ;PIGS multiprocessing flag is set, keep processing PIGS commands

we can deduce that the PIGS command $18 means to set a flag to keep processing PIGS commands. (so you don't have to keep invoking the pigs engine by a SWI command followed by a PIGS byte.) That also means that the code at $1378 that had decoded as 6800 "aba" is *NOT* aba! It is PIGS code $1b.

To end today here is the commented code for functions $18 (just explained) and it's inverse, to return from processing PIGS code to normal 6800. This is PIGS function $19. In the macro file, function $18 is called "rcflagset" and function $19 is called "ila".

;$18 set PIGS multiprocessing flag
;RC Flag Set
; Sets scripting flag allowing multiple script commands to be processed

;SAMPLE CODE:
; rcflagset

;OBJECT CODE:
; $18 ;set rc flag

;RETURNS:
; nothing

;AFFECTS:
; enables VM operation at current program counter

L10D9:
lda #$ff ;setup flag
tsx ;get current data silo pointer
sta $2,x ;save pigs multiprocessing flag
rts ;return

;$19 clear PIGS multiprocessing flag
;Inline Assembly (clear RC flag)
; Clears scripting flag disallowing script commands to be processed

;SAMPLE CODE:
; ila

;OBJECT CODE:
; $19 ;clear rc flag, switch to inline assembly

;RETURNS:
; nothing

;AFFECTS:
; suspends VM operation

L10DF:
tsx ;get current data silo pointer
clr $2,x ;clear pigs multiprocessing flag
rts ;return

Attached also is a screen shot of what the code looks like in the text editor - a little easier to read.
functions18-19 (resized).pngfunctions18-19 (resized).png

#13 4 years ago

What's that in the screenshot? A bunch of stx $ffff instructions?

Welcome to filler!

We like to see filler in Stern games, because that means there's some open space to put mods!

Change the filler from stx $ffff to:

L10E3:
db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff

Strictly speaking, you don't have to add this, you can just delete the filler and at the next bit of coding, use an org $xxxx statement there.

This happens to be at 1100:

org $1100
L1100:
; db $55
stx $FF55
L1101:
; db $62
illegal
L1102:
; db $55
illegal
L1103:
; db $63
; db $52
com $52,x
etc.

Looks like another data table!

filler (resized).pngfiller (resized).png
#14 4 years ago

Let's return to the 6800 vectors at the end of the u6 rom. We've already looked at the NMI one since it was nice and short, and the SWI one. Let's tackle the IRQ next. This one will be in pieces because it's so long. The IRQ routine can be blocked by a "SEI" (SEt Interrupt [flag]) mnemonic. This prevents the processor from getting interrupted while this flag is set - the status is not cached, so that when the interrupt flag is cleared, any interrupts that had occurred will not take place - only new ones.

On the mpu200 board, the IRQ input is attached to both PIA's U10 and U11's IRQ output pins. The interrupts are generated by circuits that feed the PIA inputs, which will trigger the IRQ output. The CPU reacts to this interrupt signal from the PIAs only.

The first part of the IRQ code decodes which of the interrupts was requesting service.

It tests for the display interrupt first, as this is the most frequent interrupt. If it isn't the display interrupt, it branches to check for the zero cross interrupt, which is the second most frequent, and the last of the timing circuitry generated interrupts. Finally, it checks to see if you pressed the self-test button on the coin door. If it was none of these 3 interrupts, the code assumes there is an error and reboots the game.

Commented code in the screen shot. We've now identified 2 more memory locations, add to your memory map: $4a, self test timer, and $5e, self test flag (ff=active).

Tomorrow's update will concern the zero cross interrupt.

interrupt1 (resized).pnginterrupt1 (resized).png
#15 4 years ago

Just so you know you're not just talking to yourself, I at least am following along. I don't understand a lot of it (assembly class was 1982), but very impressive.

#16 4 years ago

Yeah, I'm following as well. Totally out of my league but fascinating.

#17 4 years ago

Good stuff! I have done similar commented disassembly for Flash Gordon and Disco Fever.

#18 4 years ago

The Disco Fever one the one that's been floating around online forever?

#19 4 years ago
Quoted from slochar:

The Disco Fever one the one that's been floating around online forever?

I don't think I ever put my listing online, so that must be a different one. Funny how I have never bumped into it.

#20 4 years ago

Nice shutout last night Mr. Rask!

#21 4 years ago

Following...I am a little concerned over pinmame as a source file however...That machine had a curious Easter egg, (only way I can think to describe it)...If you dropped all the targets in order, it would lite special, and consequently give a free game every time you hit the warp hole...Well that is not in the manual, or even apparently on all machines, since I have owned a few of them over the years.

#22 4 years ago
Quoted from Passave:

Following...I am a little concerned over pinmame as a source file however...That machine had a curious Easter egg, (only way I can think to describe it)...If you dropped all the targets in order, it would lite special, and consequently give a free game every time you hit the warp hole...Well that is not in the manual, or even apparently on all machines, since I have owned a few of them over the years.

Sounds like a bug. If it happens to be in the source roms I'm using (they were probably just downloaded from ipdb eons ago) we'll find it. Pinmame roms are no different from roms you put in the machine..... build me machine language debugger/monitor for the actual hardware and I'll use that instead!! (Seriously.... that's been a dream since I started doing this..... I don't have the talent to bring something like that though, I can spec out what I'd like it to do but how to implement it.... not a clue)

Quoted from Tuukka:

I don't think I ever put my listing online, so that must be a different one. Funny how I have never bumped into it.

Online forever just means I downloaded it from I've no idea where a LONG time ago and it just sat in a folder somewhere. It might not even be disco fever as I think I have Phoenix the same way.... or maybe I have both?? It's definitely not from WMS themselves, though.

#23 4 years ago

Following! As a Network Engineer this fascinates me, but I have not even the faintest clue as to what I am looking at!

#24 4 years ago

This may help people follow along.

http://www.8bit-era.cz/6800.html

I had a microprocessor assembly course in college decades ago, but it was for the 8085 microprocessor. I understand what scott is doing, but it still hurts my brain to this day.

Nice work, by the way!

#25 4 years ago
zeroirq01-timers (resized).pngzeroirq01-timers (resized).png
#26 4 years ago

First part of the zero crossing interrupt, starts at L1C9C - this portion updates all timers that are used in the game. Get out your memory map worksheet and start to add some locations to it.

0046 is a continuously decrementing counter... $ff to $00 all day. Some games could use this for psudeo random results in routines. Always handy to have something on the backend reporting something different to the front end.

0047-004C are all settable timers. We've already got 004A as the self test switch timer (for debouncing), but we don't know what the others are so list them as ? TIMER for now.

004D as seen in the code, is a timer that decrements continuously $04-03-02-01-00. When it hits zero, it resets back to $04. So, it's a 4-3-2-1 step timer.

004D STEP TIMER

There are 16 timers from $4e-55 and $56-$5d. These are used as virtual switch timers. There are 5 physical switch columns in the switch matrix in the game, labeled ST0-ST4. There are also 3 virtual switch columns, the first 2 of which are timed, and the 3rd is a direct activated one. I'll label these vt5, vt6, and v7. Add to your memory map:
0043 VIRTUAL COLUMN 5 ACTION REGISTER
0044 VIRTUAL COLUMN 6 ACTION REGISTER
and although it's not called out in the code above:
0045 VIRTUAL COLUMN 7 ACTION REGISTER

The timed columns are useful for events that need to be timed, if that's not obvious. By tying the timing to a switch, this ensures the game doesn't get overloaded with timed events and subroutines. This way the switch gets processed one at a time. Even so, sometimes the game will slow down - think of meteor with a nice spinner rip, and you're hitting multiple drop banks that are lit to 7, so they're animated, and they slow down. (Each of the animation steps is a separate timed event, with the last instructions in the sequence resetting the timer for the next time through.) The virtual switch timers are only updated when the step timer goes to zero, so every 4th trip through the zero cross, the switch timers decrement. (Otherwise the timed switches would be too 'fast').

The direct column 7 action register is useful for a similar purpose, events that need to take place at some point in the future soon, but not in a timed fashion... just when the game is able. 3 of these rows are reserved for the functions "replay level 1 passed", "replay level 2 passed", and "replay level 3 passed". This is why on Big Game for instance when you pass the replay level it might take a second or 2 for the knock and the replay to be added after you surpass the score.... the game is just too busy servicing other switch columns to get to the virtual column. This way the replay won't just be lost, and you won't lose any switch performance while the replay is being awarded.

Incidentally, there is some code tightening up that can occur in this loop to save some rom space. The first set of timers can also utilize the subroutine to decrement, and just discard the A register that's only used for the timed virtual columns. Later games in fact do just this.

Tomorrow, more of the zero cross interrupt.

#27 4 years ago
interrupt2 (resized).pnginterrupt2 (resized).png
#28 4 years ago

This part of the zero cross interrupt handles some pretty important software-hardware interfaces - the solenoids turning off and the lamps updating. Next to displays, these are the 2 other items the machine uses to pass you information. The only inputs you have to the machine are via switches, which will be laid out tomorrow.

The very first part of this section concerns the match #. Add to your memory map 0033 MATCH. A note on programming in 6800 here.... the programmer chose to calculate the match overflow from 90 to $a0 with a compare statement. This could also be written (shorter) as adda #$10, daa which would have automatically handled the overflow. (DAA corrects hexadecimal numbers to their BCD equivalent.) 5 bytes would be replaced with 1, and in later games, they did do it this way. However, you must make sure the lower nibble stays as a zero for match to 'work'.... some later games have an "anda #$f0" in the match routine.

If romspace were no problem, this wouldn't be an issue. Galaxy has plenty of space in it; sometimes the choices become execution-time critical as well, especially in interrupts. One of the worst things as a programmer that can happen is having a re-entrant interrupt - trust me, you don't want this. Any changes to game code should ideally not take place in any hardware interrupt, unless it absolutely has to be there.

The next section of code checks the solenoid timer (add 0047 SOLENOID TIMER to your memory map), and if it's zero, turns off all the momentary solenoids. It actually takes the 4 solenoid lines HIGH (that's the lda #$0f, ora $92 portion) - solenoid $f on the solenoid driver board is not connected to any solenoid drive circuits, and $0 is. This is just the way the solenoid driver board works, since it's switching ground instead of switching power.

Finally, the code grabs each lamp byte and breaks it up into high and low nibbles. Older mpu100 and bally games use only one 5101 to store game state information, so there's only 4 bits available for the lamp information. Handy, because the lamp driver board is wired to take a nibble for it's address, and a nibble for the actual data. So the full byte of lamp data has to be manipulated to pass the correct nibble to the lamp board. Cleverly, the B register not only handles the lamp address nibble, it also is used to determine if the high or low nibble of lamp data is to be passed based on it being odd or even.

You also get the address of the lamp table: It starts at $13. You know there's 60 lamps, so 60/8=7.something.... add to your memory map, 0013-001A VISIBLE LAMPS.

Here also is a major way to track what routines will be doing in the stern coding - the picture below is a worksheet I use to keep track of the lamps on a game. As you can see, some of the lamps are already filled in, these are the same game to game. The top lamp is $3b, which is 60 in decimal. 60 lamps. Fill in the lamps from the schematics for your game.

Tomorrow, more of the zero cross interrupt.
lampsonly (resized).pnglampsonly (resized).png

#29 4 years ago
interrupt3 (resized).pnginterrupt3 (resized).png
#30 4 years ago

OK, this is a longer one, performing a couple of functions; first up, reading the switch matrix. Incidentally, the lines I've left uncommented are setup values for the PIA's.... and the reason I've left them uncommented is that I don't understand what it's doing 100%! It's one of those things that sometimes it's been explained (in various vintage and contemporary texts) and I'm sure I understand it, but I don't really. Since I don't ever need to change it, I'm ok with not knowing, but if someone wants a stab at it, have at it!

The first section reads the switch matrix by strobing a line, waiting/doing some housekeeping, and then reading the results from the PIA. There is something called propagation delay in interface chips, and the code is written to be long enough to allow this to happen so there are valid results waiting in the PIA register. There's references to $39, offset +5, and offset +$A. Since we know there are 5 physical switch columns in the machine according to the schematics, (labeled ST0-A, ST1-B, ST2-C,ST3-D,ST4-E) there's the offset 5, and the further offset $A (10, 2*5). The switches are pulsed in reverse strobe order, so the first pulse goes to ST4, and its ultimate destination will be $38, $3D, and if valid switch detected, $42. I call these columns history, initial, and action registers. Most of the time I swap the history and initial registers' monikers by mistake and I did so above (I think)..... the important register to me in software modding is the "action" register - this is the register that the foreground program reacts to. (The switch test uses one of the other registers, which is a way it can be fooled, as does the "fast react" solenoids, which will be covered in tomorrow's zero cross update).

If anyone is interested in exactly how the switch registers work I will make a separate message detailing that, where I'll trace a set of bytes through the entire routine. So far into the Galaxy code we've done a lot of back end stuff that frankly, you probably won't ever touch when modifying gameplay code.

Add to your memory map, SWITCH HISTORY 0034-0038, SWITCH INITIAL 0039-003D, SWITCH ACTION 003E-0042, and VIRTUAL ACTION 0043-0045. The virtual action switches are the timed switches from earlier in the zero cross; they appear as action columns only because the software activates them, thus why they are "virtual" switches. You WILL make use of these at some point in your mods (for instance, to turn off something that you turned on previously and need to run for a certain amount of time.... yes, you could use delay functions, but it's not as efficient, since the timing is built into the OS, might as well use it.... assuming there's free ones! There is at least one free in galaxy.)

Next up in the interrupt, there's some code that reacts to a blinking mask timeout which will start a player's score blinking after a certain amount of time elapsed without scoring. This is one of the times that something gets set in the foreground (the scoring) that the background (the blink reaction) will react to. Every time score happens, the blink timer is reset. In the background, every time the timer is zero, it flips the current player's blink mask (a flag that tells the game to either show a player's info, or just show blanks). It only does it for valid players in the game. So we've identified more memory locations for the memory map:
0061 VALID PLAYERS (1234 5xxx) - each set bit corresponds to the players 1-5. "Player 5" is the credit/ball in play display, and is only set valid/invalid in the self test and some other modes, never in game mode)
0062 P1 DISPLAY MASK ($FF=BLANK, $00=SHOW)
0063 P2 DISPLAY MASK ($FF=BLANK, $00=SHOW)
0064 P3 DISPLAY MASK ($FF=BLANK, $00=SHOW)
0065 P4 DISPLAY MASK ($FF=BLANK, $00=SHOW)
0066 P5 DISPLAY MASK ($FF=BLANK, $00=SHOW)

The final part of this portion of the zero cross is the section that handles blinking lamps. Update memory map: 0049 ? TIMER to 0049 LAMP BLINK TIMER. If you want faster blinking you can change the blink constant from #$0c to less, or make it slower. Different games use different constants here.
The blink code takes offsets from the lamp table +$11 to get it's info on which lamps to flip. So, the lamp table runs from $13-$1a, so the blink table runs from $24-2b. Add to memory map 0024-002A BLINK LAMP TABLE.

The final lines of code check another timer, which is the sound timer. When it's zero, time to process sounds, and its timer is 004C. Update the memory map from 004C ? TIMER to 004C SOUND TIMER.

Tomorrow, the rest (finally!) of the zero cross interrupt code. I will not be posting the sound code portion of it just yet, though, as it's rather long and convoluted. There's more processing done in the zero cross interrupt across all areas of the game than anywhere else.... it's hard to believe the entire interrupt gets processed 120 times a second. (Or, I guess, 100 times a second in 50 Hz countries).

#31 4 years ago
interrupt4 (resized).pnginterrupt4 (resized).png
#32 4 years ago

You have spent hours doing this,why???? Do you want to change the Gameplay somehow???You mention dropping drop targets "in order" to lite a special,what order??I've owned my Galaxy for 10 yrs now and haven't found any order to the drop targets!!What does all this mystery coding get you????Thanks for posting!!!

#33 4 years ago

The last part of the zero cross interrupt fires all the fast react solenoids. They're fast react because this code portion reads the switch matrix initial and history registers and fires any solenoid that is closed for one less cycle than a normal switch activation - this is why sometimes you will get a pop or sling firing that doesn't score - the score is a separate, normal switch routine, and the firing is done by the above routine in the interrupt.

The 'active game flag' is similar to a lamp - in fact, the flags are stored right after the lamps in memory, and are labeled right after them as well, AND the pigs code for them is the same as the lamps. So setting/checking/clearing a flag is exactly the same as the equivalent for lamps. The Flags are labeled from $3c-$87. Lamp byte $1a contains lamps $38-$3b in the lower nibble, and flags $3c-$3f in the upper nibble.

Add to your memory map, FLAGS 001B-0023. The dip switches are flags as well, occupying the upper 4 bytes of the flag table, so add DIPS 0020-0023.

This leaves a 'hole' in the memory map, and a quirk of the PIGS system that I believe they would have fixed in 1979 had they thought about it a little bit. Remember last time where we discussed the blinking lamps, which are offset +$11 from the lamps? The first 5 columns (the non-dips) of the flags have a blink area +$11 also, although they aren't blinked by the blink routine. (They ARE affected by the lamp commands, though). This removed 5 valuable bytes from the 6810 main ram.... I believe they would have fixed the lamp routines to use these bytes, as many mpu200 games are in dire need of ram. (Meteor itself reuses ram, which causes the infamous "endless bonus" countdown bug) So add to your memory map $002C-0031 BLINK OVERFLOW.

So, back to the zero cross fast react solenoids- the code takes the switch column 1 register and just shifts through it looking for set bits, which would be switches that are closed and need to be fired. Once it finds a set bit, it branches to fire it, checking to see if another solenoid is firing first - if it is, no fast react fire at all! This is why on 9 ball sometimes the pop bumper doesn't pop, but scores, when one of the 3 bank drop targets is resetting - drop banks have a particularly long dwell time on reset. The solenoid is added to the lower nibble of the PIA register, saved to fire it, and the standard solenoid timeout value of $05 is saved in the timer. Different mpu200 games that have 'holes' in the fast react switch column may have had fast react solenoids removed (Free Fall comes to mind) but usually the code is changed to account for this (asla, asla, instead of just one, to skip the missing row).

The pictures below show the fast react portion of the switch matrix, and also the newly updated worksheet that shows the entire lamp/flag/dip tables with their addresses and designations. Knowing what each flag does is key to being able to modify the rules of a game.

And finally, right at the bottom of the code, some more filler! Galaxy has a lot of filler, so lots of room for experimentation.

fastreact (resized).pngfastreact (resized).pngblank-stern-lamp-flag-grid-lamps13 (resized).pngblank-stern-lamp-flag-grid-lamps13 (resized).png

#34 4 years ago
Quoted from hawkmoon:

You have spent hours doing this,why???? Do you want to change the Gameplay somehow???You mention dropping drop targets "in order" to lite a special,what order??I've owned my Galaxy for 10 yrs now and haven't found any order to the drop targets!!What does all this mystery coding get you????Thanks for posting!!!

First of all,,,,,, that's Passave that was remarking on the drop target "easter egg" (one man's easter egg is another man's bug)......

Secondly,,,,,, maybe,,,,,,, yes,,,,,, the gameplay might be changed!!!!!!!!!!!!!!!!!!!!!Thirdly,,,,,,sometimes you just climb the mountain because it is there do you not????????????????Also,,,,,,,I think some of your punctuation keys might be stuck!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#36 4 years ago
reset1 (resized).pngreset1 (resized).png
#37 4 years ago

Onto the last 6800 vector, the reset vector. Almost all programs disable interrupts as their first instruction as nothing is set up yet (if an interrupt occurs without a valid stack, the program is going to crash as you don't know if RAM is even installed in the system!) The first section of the code sets up PIAs and the sound card. You could add some sound card and PIA registers to your memory map, 00A0-00A8 and 00C0-00C3 SOUND CARD REGISTERS, 0088-008B U10 PIA REGISTERS and 0090-0093 U11 PIA registers. The LED flicker people talk about is the LED being ON when the machine is turned on, then the first section of code (the sta $93) turns OFF the LED.

Then, the rom checksum code - the checksum in Stern games is just a rolling addition of all the bytes that must sum to $00. No carries, no checking sections, just all the bytes. In the code later on there will be checksum correction bytes, at least one, but often more as the code was likely written and tested in sections, then debugged without changing some of the eproms. If the checksum is correct, branch to flash the LED and make the 'ding'.

Rams are tested next. u7 the 6810, and u8/13 the 5101s. The test is essentially the same except that the 5101 ram test preserves the byte that was currently under test and restores it at the end. This makes sense since it's the battery backed up NVram and shouldn't be changed. It should be noted here that there is NO CHECKSUM for the nvram! Invalid contents often are stored in the nvram especially after a dead battery/battery change - most games handle invalid contents gracefully but many do not. Good idea to go to each audit location after a battery swap and clear each one manually.

This is likely the shortest explanation of a section you'll see, at least until tomorrow's where the rest of the LED flash sequence is laid out.

#38 4 years ago
Quoted from slochar:

then the first section of code (the sta $93) turns OFF the LED.

Actually the MPU LED is switched off at line 5862, not 5839.

I hope it's ok that I added the 6821 PIA comments:

MPU-200_Galaxy_DSM1.pngMPU-200_Galaxy_DSM1.png
#39 4 years ago
Quoted from Quench:

Actually the MPU LED is switched off at line 5862, not 5839.
I hope it's ok that I added the 6821 PIA comments:
[quoted image]

Question: What is "J5 interrupts"? (Line 5839..)

#40 4 years ago

Quench sure go ahead and correct/add as you see fit. Like I said previously, PIA's are a mystery to me, and I've used your code to try and really understand what's going on in them, but it's elusive. Makes me want to build one of the kits from the 70s where you flip switches to do stuff on them.

Coyote there's an interrupt pin on the J5 connector, probably for the sb300 to interrupt the cpu (which I don't think ever was programmed) or a for an external in-system debugger setup. (I know what the williams one of those looked like, but no idea what the Bally one looked like, and if Stern even had one)

#41 4 years ago
Quoted from Coyote:

Question: What is "J5 interrupts"? (Line 5839..)

It was a spare unused interrupt source that Bally routed (and Stern copied) to the J5 connector for future expansion, maybe an inhouse debugger as slochar mentioned - possibly some exotic sound boards in the European conversion game kits may use it.

The Stern VSU-100 speech boards are wired to this spare PIA interrupt but not from the MPU connector J5 pin 32, rather MPU J4 pin 11 which was specially wired on the MPU-200 board. The speech board uses it to notify the CPU it's busy speaking.

Quoted from slochar:

probably for the sb300 to interrupt the cpu (which I don't think ever was programmed) or a for an external in-system debugger setup.

For some reason Stern added an extra pin (34) to the J5 connector that directly interrupts the CPU IRQ input without PIA control. While the programmable timer chip on the SB300 is wired to it, I don't think the feature is actually used.

Quoted from slochar:

@Quench sure go ahead and correct/add as you see fit.

Cheers, I'll try to add the PIA comments in the zero crossing interrupt handler in a few days.

#42 4 years ago

Ah, thanks guys, good to know!

#43 4 years ago

Just some more tests of components. Note that the external circuits that provide the zero cross interrupt signal and the display interrupt signal do nothing to test the FREQUENCY of those signals - it's just looking for one interrupt request. Sometimes the 555 timer in the display interrupt or its associated components to set the timing can be bad, and the self test will "pass" - but the displays won't work correctly. Don't be fooled that the sixth and seventh flash 'test' anything more than one interrupt.

The seventh and final flash depends on the solenoid fuse being good, as the zero cross derives from that circuit. (There's no stiffening cap or smoothing circuitry on the solenoid bridge, so it's actually pulsing DC, which is what the zero cross circuit is detecting - as the voltage on each AC sine wave approaches zero. The bridge always keeps the voltage positive)

reset2 (resized).pngreset2 (resized).png
#44 4 years ago

slochar I love this thread, it's my most favorite Pinside thread ever! Please keep it coming

I'm sure there are a lot of people watching this thread that have a vague idea of what you are talking about (like me) and have always been intrigued by the internal code workings of these wonderful games. Amazing to see what they did back then with the limited tools and memory resources.

#45 4 years ago

This is the best thread ever created here!

I "try" to do asm level rom hacks to games, and my depth of knowledge of asm is baby pool mode, so it is really useful to see a complete train of thought for approaching a dump, massage, modify process.

If I had read this when I was trying to work through bad cats, I may have made more progress before I got too busy to work on it and probably would have revisited it.

Thank you so much for all of the detail!

#46 4 years ago

Oh, my, my, Dont make me beg for that audio file too...Because I will if I have to.I will... (Typed with a certain amount of crazy in Jenns eyes)Thx

#47 4 years ago

Here's the final part of system startup (minus a couple of PIA initialization routines). Also the first peek at the macros that I use to assemble PIGS code.

All of the macros without parameters are one byte - so one of the advantages of having this interpreted language in the first place is rom space savings. Instead of having some function like JSR $SHOW_HSTD (3 bytes) you have a function show_hstd, one byte. Or for the lamp control, instead of LDA #$32, JSR $LAMPON (5 bytes) you have lampbvalueon $32 (2 bytes). Over the course of a rom this adds us to a lot.

Incidentally, I didn't come up with the PIGS function names - Oliver Kaegi http://www.pinball4you.ch/okaegi/ and others did most of the work in this area. No one has any idea what Stern used back in the day; I would love to see the original source code for some of these games. (Does anyone have the Bally Kiss code that was around for a while and disappeared? I've seen original WMS' code from this era but nothing from Stern, Bally, or Gottlieb)

I use a macro file I've created to enable me to seamlessly compile 6800/pigs code. (The actual goal of this classic stern disassembly business is to provide recompilable commented source for all the classic stern games that will compile to 100% the same as stock roms, so that changes can be made, or just for educational purposes.... or if someone wants to write a 'new' PIGS game.)

Tomorrow will be the heart of the 'main loop' - the switch dispatch routine.

To sum up so far what is happening in the game at this point, at the foreground level, you have the main loop running:

;main loop
L137E:
lds #$027F ;set stack back to the top
jsr L1492 ;process any closed switches
bra L137E ;keep doing it

So with no switches closing, the game just repeats this loop over and over again. (Switch dispatch does a couple of other things besides switch processing)

In the background, you have 2 interrupts running, the display interrupt runs between 320-420 times a second, and the zero cross interrupt is running either 100 or 120 times a second (50 hz vs 60 hz main voltage frequency.)

reset3final (resized).pngreset3final (resized).png
#48 4 years ago
Quoted from thedefog:

This is the best thread ever created here!
I "try" to do asm level rom hacks to games, and my depth of knowledge of asm is baby pool mode, so it is really useful to see a complete train of thought for approaching a dump, massage, modify process.
If I had read this when I was trying to work through bad cats, I may have made more progress before I got too busy to work on it and probably would have revisited it.
Thank you so much for all of the detail!

Oh, if I just want to small change I don't bother with the full disassembly. The record was probably 10 minutes for someone that didn't want the silverball mania center hoop to spot a letter. (It helps that I'm intimately familiar with the bally code, though.... more like 2 hours otherwise).

Bad Cats is tough because you have to deal with Pinbol (williams equivalent to PIGS) as well as 6800. Until you get to WPC I'm pretty well versed in all the earlier stuff (except Gottlieb and the oddball makers like Gameplan). I tried to buy a sharpshooter last year at pinfest so that I could become versed in it but for some reason my lowball offer of $400 wasn't accepted. Or maybe it was $300

Quoted from woz:

slochar I love this thread, it's my most favorite Pinside thread ever! Please keep it coming
I'm sure there are a lot of people watching this thread that have a vague idea of what you are talking about (like me) and have always been intrigued by the internal code workings of these wonderful games. Amazing to see what they did back then with the limited tools and memory resources.

And to pare those limited resources down even more to make room for mods..... even today it's every trick I've ever seen or read about although I haven't used one last trick... dynamically created code in the bottom of the stack to copy routines that are all ALMOST the same, but modifying a byte or 2 for custom work. That's when you get REALLY desperate, and really, works better on games with more RAM available. (Williams system 7-11 games actually have a routine to do something similar to this, although they don't really modify anything on the way down, but they conceivably COULD.) I'd probably use the bottom of the stack for this type of thing, but some games already use that area!!

Later mpu200 games are chock FULL. Every one of them is going to need/get the crunching I applied to 9 ball and flight 2k if people want to make mods to them.

#49 4 years ago
function 26 switch dispatch (resized).pngfunction 26 switch dispatch (resized).png
#50 4 years ago

Based on the code above, we now know that the previously unknown data at $1100 is the switch matrix vector (jump) table. Using the Galaxy manual I've filled in the switches - some of the ones that the manual lists as "open" in fact point to actual routines. When this happens it's one of a couple things: There were some switches in the game that were removed, but the code remains; or, they are additional virtual switches.

The virtual switch idea is something that other manufacturer's (i.e. Bally) probably should have copied. Williams does have a switch function to trip switches virtually but they didn't use it as much as Stern does. (The Williams' switch handling (system 7 and up) in its detection process is far superior to anything that Bally or Stern put out there, but that's another thread for another day.)

Tomorrow we'll look at the score process as used by the switch dispatch routine, one of which also encompasses the PIGS function $4f, score directly, so we'll look at that too. Next day will be the other score function, $56, add to score queue.

L1100-switch matrix vector table (resized).pngL1100-switch matrix vector table (resized).png
Promoted items from Pinside Marketplace and Pinside Shops!
From: $ 14.00
Electronics
Third Coast Pinball
 
$ 3.00
Wanted
Machine - Wanted
Jeffersonville, IN
From: $ 30.00
Cabinet Parts
Rocket City Pinball
 
$ 12.00
Playfield - Toys/Add-ons
UpKick Pinball
 
$ 29.00
Cabinet - Sound/Speakers
RoyGBev Pinball
 
$ 119.95
Boards
Allteksystems
 
From: $ 169.00
From: $ 170.00
$ 169.00
Wanted
Machine - Wanted
Middletown, OH
$ 10.00
$ 69.00
$ 12.00
Electronics
Yorktown Arcade Supply
 
Hey modders!
Your shop name here
There are 175 posts in this topic. You are on page 1 of 4.

Reply

Wanna join the discussion? Please sign in to reply to this topic.

Hey there! Welcome to Pinside!

Donate to Pinside

Great to see you're enjoying Pinside! Did you know Pinside is able to run without any 3rd-party banners or ads, thanks to the support from our visitors? Please consider a donation to Pinside and get anext to your username to show for it! Or better yet, subscribe to Pinside+!


This page was printed from https://pinside.com/pinball/forum/topic/stern-galaxy-code-disassembly/page/1 and we tried optimising it for printing. Some page elements may have been deliberately hidden.

Scan the QR code on the left to jump to the URL this document was printed from.