It is currently Tue Dec 11, 2018 4:53 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 47 posts ]  Go to page Previous  1, 2, 3, 4  Next
Author Message
PostPosted: Fri Sep 28, 2018 4:22 pm 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
I have been able to deduce for 3D Block how the main game code communicates with the PIC, and what data is being sent and returned.

As we know, the game can only communicate with the PIC through the upper twelve bits of the CPU address bus. The game does this by calling wait loop procedures that are spaced 16 bytes apart. In the case of 3D Block, there is one wait loop procedure each at E180, E190... E1F0. The PIC sees address E18x for several cycles and concludes that its function 0 is to be executed; it sees address E1Fx and concludes that its function 7 is to be executed --- you get the idea. Both the functions and the addresses that trigger them are game-specific; 3D Block's are incompatible to Block Force's. 3D Block's PIC functions are:
  • Function 0: Main game screen split --- raise one IRQ about 12,400 M2 cycles later.
  • Function 1: Clear internal 32-bit register and prepare for further data --- raise one IRQ immediately to acknowledge.
  • Function 2: Start raising IRQs timed such that a counter in the main game, increased on every IRQ being generated, reaches 247, no lower, no higher. (I don't know the exact timing values, only the result that must be reached in the main game.)
  • Function 3: Send a 1 bit to the PIC. Raise one IRQ immediately to acknowledge.
  • Function 4: Level introduction screen split --- raise several IRQs about 2,175-or-so cycles apart. I don't know the exact duration or how many; I'm basing this on the fact that the pattern table is switched after the IRQ handler has been called six times, and the pattern switch still needs to be about mid-screen for the playfield to not look wong.
  • Function 5: Send a 0 bit to the PIC. Raise one IRQ immediately to acknowledge.
  • Function 6: Receive a result byte from the PIC. The number of IRQs generated resembles the result byte value and can be 0 if no IRQ is generated.
  • Function 7: Reset the IRQ generation counter for subfunction 2.
Functions 7 and 2 are used on the title screen to set the byte at $02D1 to its proper value of $F7. Wrong value, and the blocks that have fallen to the ground already will be displayed incorrectly.
Functions 1, 3 and 5 are used in-game whenever the wire-frame pieces are being redrawn. The main game packs most of its game state into four bytes and sends them to the PIC. After receiving 32 bits, the PIC apparently starts processing them.
Function 6 is called four times to receive four result bytes. Basically, based on the current game state that was sent before, the PIC spits out the next position of the current piece. That is why not emulating the PIC causes the pieces to never move. Mercifully, the proper game logic can be deduced from the later 1990 release of the game.
Functions 0 and 4 are only cosmetic and cause pattern table switches and X position updates in the case of 4.

Block Force has sixteen functions from FE00 to FEF0, again spaced in 16-byte distances:
  • Function 12: status bar during game play. Raise one IRQ after about 25,000 M2 cycles.
  • Function 15: something at bootup. I just raise one IRQ after 6,000 M2 cycles.
  • Functions 9 and 11: unknown, but raising one IRQ immediately minimizes the pauses.
Wait procedure 0 immediately jumps to wait procedure 8, 1 to 9, and so on. Apparently, this version of the PIC program will not execute the same function twice, so functions 0-7 seem to be dummy functions to enable executing functions 8-15 multiple times in a row.

This is all preliminary, because I have not tested the games at length. Block Force is supposed to display an initial high score of 006502, which it does not with the above description. But it's playable with the status bar.

Try a current build of NintendulatorNRS if you want, and be sure to set your unmodified ROM images to NES 2.0 Mapper 355. Credit to krzysiobal for pointing out the 16-bytes-spaced wait loops that signal a function request to the PIC, and credit to me for determining what the subfunctions that have been identified do.

This does not remove the need to have both games' PIC prgoram to be dumped.


Top
 Profile  
 
PostPosted: Fri Sep 28, 2018 8:41 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
NewRisingSun wrote:
Wait procedure 0 immediately jumps to wait procedure 8, 1 to 9, and so on. Apparently, this version of the PIC program will not execute the same function twice, so functions 0-7 seem to be dummy functions to enable executing functions 8-15 multiple times in a row.
I bet that's for better synchronization. An iteration through the tightest possible poll loop takes 3 PIC instruction cycles, or 12 M2. To check for a specific value requires more instructions—loading the value, XORing it, check the flag, repeat—but just polling for a single pin's rising edge is tighter:

Somewhere in Block Force's PIC there's assuredly going to be a pair of instructions:
Code:
btfss PORTB, 1 ;  0x726 → when enciphered should read back as 3
goto $-2 ; encoding unknown; no relative address instructions in 12-bit PIC


CPU A14 through CPU A7 are available on the PIC's 8-bit wide "port B", while CPU A6 through A4 are are the 4-bit wide "port A". This means that the PIC can literally just use PORTA to select the requested function.

Since the poll loop in 3D Block will involve looking for the address $E180, that will be the byte 0xC3 inside the PIC. ((0xE180 >> 7) & 0xFF). It'll most likely show up as either movlw 0xC3 / xorwf PORTB, w (0xCC3 / 0x186 → 3 / F) or movf PORTB, w / xorlw 0xC3 (0x206 / 0xFC3 → 4 / 0) although they could have written the code in various other obfuscatory ways. not C3. E2. Bits are shuffled.


Last edited by lidnariq on Sun Sep 30, 2018 7:03 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sat Sep 29, 2018 7:01 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
I have been able to gain a better understanding of Block Force's PIC functions.

As mentioned before (and as originally found out by Krzysiobal), function 0 is invoked by calling a wait loop at $FE00, function 15 at $FEF0, and so on. The wait loops at functions 0-7 directly jump to the wait loop of the respective function +8. The function is therefore invoked by a rising edge of CPU A7. This was not the case with 3D Block.
  • Function 0: Set PIC RAM address to 0.
  • Function 1: Increase PIC RAM address by 1.
  • Function 2: Set the byte at the current PIC RAM address to $00.
  • Function 3: Increase the byte at the current PIC RAM address.
  • Function 4: Generate an IRQ after about 25,000 M2 cycles. This function is used to time the status bar during gameplay.
  • Function 5: Replace the high score at RAM $1A-$1F with the current score at RAM $14-$19 if the current score is higher.
  • Function 6: Load the PIC latch with the byte at the current PIC RAM address.
  • Function 7: Decrease the PIC latch, and generate an IRQ if it has become zero after decreasing.
The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode, but I have no idea why. I don't know whether the functions other than 4 and 7 generate an IRQ at all, to acknowledge the request; the game would ignore the IRQ if they do.

I have updated the NintendulatorNRS build linked to in my previous post.


Last edited by NewRisingSun on Sun Sep 30, 2018 8:58 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sat Sep 29, 2018 11:17 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
NewRisingSun wrote:
The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode, but I have no idea why. I don't know whether the functions other than 4 and 7 generate an IRQ at all, to acknowledge the request; the game would ignore the IRQ if they do.
Huh. That's probably directly manipulating the PIC's pointer register ("File Selection Register"). The PIC16C54 only has 25 bytes of RAM (addresses 7-0x1F), in 32 addressable locations. The other 7 addresses are so-called "Special Function Registers" and control things like indirect branching, the current pin i/o values, and a few other things.

Because of this, we should be able to read PCL ("Program Counter Low byte", address 2) to figure out where in the PIC's memory space function 6 is.


Top
 Profile  
 
PostPosted: Sun Sep 30, 2018 10:47 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
Do we have the enciphered dump of Block Force's PIC anywhere?


Top
 Profile  
 
PostPosted: Sun Sep 30, 2018 11:00 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
Right here as an attachment. Or maybe I am misunderstanding your request.


Top
 Profile  
 
PostPosted: Sun Sep 30, 2018 11:07 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
I thought that one was 3D block?


Top
 Profile  
 
PostPosted: Sun Sep 30, 2018 11:14 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
No, the PCB is from a Block Force cartridge. The PCB has just 3-D Block written on it, apparently because Block Force reuses that game's PCB, with different EPROM and PIC ROM content.


Top
 Profile  
 
PostPosted: Sun Sep 30, 2018 3:32 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
NewRisingSun wrote:
The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00").
Does the 6502 tell the PIC to do this? Or does the PIC need to have done this on its own?Oh, it's the PIC.

I've been playing "jigsaw puzzle" with the dump, and am pretty certain of this small handful of instructions starting at word 3:
Code:
03 c movlw 0xFF
04 6 tris PORTB
05 e movlw 2
06 9 movwf 0x1A
07 c movlw 0
08 8 movwf 0x1B
09 9 movlw 5
0a f movwf 0x1C
0b a movlw 6
0c e movwf 0x1D
0d c movlw 0
0e d movwf 0x1E
0f c movlw 0
10 c movwf 0x1F


slightly lower confidence for the immediate next eight:
Code:
11 c  movlw 0xdd ; 0xDD corresponds to address 0xFE00
12 f  xorwf PORTB, w
13 0  btfss STATUS,Z ; wait until address bus matches
14 a  goto 0x11
15 c  movlw 0xdd
16 f  xorwf PORTB, w
17 1  btfsc STATUS,Z ; wait until address bus mismatch
18 e  goto 0x15


a hunch:
Code:
30 2 clrf FSR ; f'n 0
31 a incfsz 7,f
32 c incf FSR,f ; f'n 1
33 a incfsz 7,f
34 6 clrf INDF ; f'n 2
35 a incfsz 7,f
36 8 incf INDF,f ; f'n 3
37 a incfsz 7,f


There's also an interesting set of almost-repeats starting at word 4F:
AA61846
AB71146
A441946
A551046
A621E46
A713746
.... I wonder if that's the routine that copies over the score? I can't figure out why it takes 7 instructions per byte instead of 2, though.


Top
 Profile  
 
PostPosted: Mon Oct 01, 2018 5:58 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
NewRisingSun wrote:
Function 5: Replace the high score at RAM $1A-$1F with the current score at RAM $14-$19 if the current score is higher.
PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode
Have you seen it use memory addresses other than 8, 9, and 0x14-0x1F ?

Does krzysiobal have the ability to test the PIC's reactions to certain queries? I'm curious what the result is if one tries to read from PIC RAM addresses 0-7.

The repetitions starting at word 4F are probably the bit that compare the score at $1A-$1F with the score at $14-$19, something like this:
Code:
4f a
50 a  movf 0x19,w ; {a,b,4,5,6,7} ^ 3 = {9,8,7,6,5,4}
51 6  subwf 0x1f,w ; {6,7,4,5,2,3} ^ 9 = {f,e,d,c,b,a}
52 1  btfsc STATUS,Z
53 8  goto 0x57 (8,1,9,0,e) is +7 before enciphering
54 4  btfss STATUS,C
55 6  goto ?


Top
 Profile  
 
PostPosted: Mon Oct 01, 2018 7:08 pm 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
lidnariq wrote:
Have you seen it use memory addresses other than 8, 9, and 0x14-0x1F ?
It uses addresses 0x10 and 0x11 to store values that are immediately returned, basically to test the PIC's function, because it deliberately goes on a code path to nowhere if the just-written value is not properly returned.


Top
 Profile  
 
PostPosted: Tue Oct 02, 2018 12:07 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
If the PIC only asserted /IRQ for one Tcy (four M2) would that pose a problem for the existing 2A03 code?

Hunch:
Code:
48 2   movf INDF, w ??? ; f'n 6????
49 5   movwf 7
4a a   goto 0x11 ???
4b b   decfsz 7,f ; f'n 7???
4c a   goto 0x11 ???
4d 1   bcf PORTA,0 ???
4e 0   bsf PORTA,0 ???
4f a   goto 0x11 ???


Top
 Profile  
 
PostPosted: Tue Oct 02, 2018 12:47 pm 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
That would depend on what the 6502 CPU does when an IRQ is raised while an instruction is executing but lowered before that same instruction is finished --- is the IRQ lost? If so, then yes, it would be a problem, otherwise no.


Top
 Profile  
 
PostPosted: Tue Oct 02, 2018 1:08 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7807
Location: Seattle
Yeah, my understanding is that the 6502 will only dispatch an IRQ if /IRQ is asserted during the φ1 of the penultimate cycle of an instruction. (Right?)

The reason I ask is I see that "1 / 0" pattern in two places, which could mean "drive /IRQ low then drive /IRQ high" (bcf PORTA,0 / bsf PORTA,0) and no obvious other places where 1 and 0 are separated by other instructions. Which in turn means it would only be asserted for 4 M2, which makes it highly dependent on the exact code that's interrupted as to whether the 6502 would receive them correctly. (Hence asking)

The other possibility is that it's actually using the tris PORTA instruction to toggle the line between driven low and hiZ; tris PORTA will encipher to 5. There's only one place that could be, but it's not really all that plausible:

unlikely:
Code:
3c f movlw 0xde
3d 5 tris PORTA
3e e movlw 0xdf
3f 5 tris PORTA
Generally one doesn't put a random-feeling number like "0xd" in the don't care bits.


Top
 Profile  
 
PostPosted: Tue Oct 02, 2018 1:27 pm 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 709
The 6502 code is extremely obfuscated. We're talking replacing a single STA by a mountain of indirect-indirect-indirectly-addressed transfers, which take 5 or 6 cycles each.

And when reading PIC RAM, the 6502 code cannot afford to miss the IRQ, because it works like this (greatly simplified):
Code:
   JSR   ReadPICValue
   ; (...)
   
   ; subroutine
ReadPICValue:
   JSR   PIC_LoadLatch   ; fn#6
   LDA   #0
   STA   value+0
   STA   value+1
loopy:
   JSR   PIC_DecLatch   ; fn#7
   CLC
   LDA   value+0
   ADC   #1
   STA   value+0
   LDA   value+1
   ADC   #0
   STA   value+1
   JMP   loopy
   ; no RTS!
   
IRQhandler:
   PLA
   PLA
   PLA
   RTS
   


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 47 posts ]  Go to page Previous  1, 2, 3, 4  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot] and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group